summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp13
-rw-r--r--Android.bp2
-rw-r--r--apct-tests/perftests/inputmethod/AndroidManifest.xml2
-rw-r--r--apex/jobscheduler/service/aconfig/job.aconfig10
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java20
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java105
-rw-r--r--apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java3
-rw-r--r--config/Android.bp6
-rw-r--r--core/api/current.txt1
-rw-r--r--core/api/system-current.txt1
-rw-r--r--core/java/android/app/ActivityManager.java173
-rw-r--r--core/java/android/app/IActivityManager.aidl8
-rw-r--r--core/java/android/app/StatusBarManager.java41
-rw-r--r--core/java/android/app/admin/DevicePolicyManagerInternal.java5
-rw-r--r--core/java/android/app/admin/flags/flags.aconfig29
-rw-r--r--core/java/android/app/servertransaction/ClientTransactionListenerController.java9
-rw-r--r--core/java/android/content/pm/PackageManager.java10
-rw-r--r--core/java/android/content/pm/multiuser.aconfig7
-rw-r--r--core/java/android/hardware/OverlayProperties.java3
-rw-r--r--core/java/android/hardware/face/FaceCallback.java321
-rw-r--r--core/java/android/hardware/face/FaceManager.java326
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintCallback.java299
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintManager.java358
-rw-r--r--core/java/android/permission/IPermissionManager.aidl2
-rw-r--r--core/java/android/permission/PermissionManager.java15
-rw-r--r--core/java/android/permission/flags.aconfig24
-rw-r--r--core/java/android/provider/Settings.java10
-rw-r--r--core/java/android/security/IFileIntegrityService.aidl2
-rw-r--r--core/java/android/util/FeatureFlagUtils.java8
-rw-r--r--core/java/android/view/AccessibilityInteractionController.java10
-rw-r--r--core/java/android/view/View.java9
-rw-r--r--core/java/android/view/ViewRootImpl.java30
-rw-r--r--core/java/android/view/accessibility/AccessibilityInteractionClient.java10
-rw-r--r--core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl6
-rw-r--r--core/java/android/view/accessibility/flags/accessibility_flags.aconfig10
-rw-r--r--core/java/android/window/WindowTokenClient.java30
-rw-r--r--core/java/com/android/internal/policy/PhoneWindow.java3
-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/java/com/android/internal/widget/ActionBarOverlayLayout.java2
-rw-r--r--core/jni/Android.bp28
-rw-r--r--core/jni/android_view_MotionEvent.cpp102
-rw-r--r--core/jni/android_view_Surface.cpp21
-rw-r--r--core/jni/include/android_runtime/android_view_Surface.h1
-rw-r--r--core/jni/platform/OWNERS4
-rw-r--r--core/jni/platform/host/HostRuntime.cpp (renamed from core/jni/LayoutlibLoader.cpp)36
-rw-r--r--core/jni/platform/host/native_window_jni.cpp31
-rw-r--r--core/proto/android/providers/settings/secure.proto1
-rw-r--r--core/res/res/drawable-nodpi/platlogo.xml212
-rw-r--r--core/res/res/drawable-nodpi/stat_sys_adb.xml48
-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/attrs.xml17
-rw-r--r--core/res/res/values/config.xml4
-rw-r--r--core/res/res/values/config_telephony.xml6
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--core/tests/FileSystemUtilsTest/TEST_MAPPING2
-rw-r--r--core/tests/coretests/src/android/app/ActivityManagerTest.java2
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java38
-rw-r--r--core/tests/coretests/src/android/hardware/face/FaceManagerTest.java37
-rw-r--r--core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java37
-rw-r--r--core/tests/coretests/src/android/view/ViewRootImplTest.java12
-rw-r--r--core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java11
-rw-r--r--core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java78
-rw-r--r--data/etc/privapp-permissions-platform.xml2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java45
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java46
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java134
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt216
-rw-r--r--libs/hostgraphics/gui/Surface.h2
-rw-r--r--libs/hwui/Android.bp19
-rw-r--r--libs/hwui/jni/HardwareBufferHelpers.cpp2
-rw-r--r--libs/hwui/jni/android_graphics_HardwareRenderer.cpp26
-rw-r--r--libs/hwui/platform/darwin/utils/SharedLib.cpp33
-rw-r--r--libs/hwui/platform/linux/utils/SharedLib.cpp33
-rw-r--r--libs/hwui/utils/SharedLib.h34
-rw-r--r--location/java/android/location/flags/location.aconfig7
-rw-r--r--media/java/android/media/MediaCodec.java41
-rw-r--r--media/java/android/media/MediaDrm.java17
-rw-r--r--media/java/android/media/midi/package.html10
-rw-r--r--media/java/android/media/session/PlaybackState.java6
-rw-r--r--media/jni/Android.bp2
-rw-r--r--media/jni/android_media_MediaDrm.cpp43
-rw-r--r--media/jni/android_media_MediaDrm.h2
-rw-r--r--nfc/java/android/nfc/INfcAdapter.aidl3
-rw-r--r--nfc/java/android/nfc/NfcAdapter.java5
-rw-r--r--nfc/java/android/nfc/cardemulation/HostApduService.java13
-rw-r--r--nfc/java/android/nfc/cardemulation/PollingFrame.aidl (renamed from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt)12
-rw-r--r--nfc/java/android/nfc/cardemulation/PollingFrame.java41
-rw-r--r--packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java29
-rw-r--r--packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java115
-rw-r--r--packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java103
-rw-r--r--packages/CrashRecovery/services/java/com/android/utils/FileUtils.java128
-rw-r--r--packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java46
-rw-r--r--packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java188
-rw-r--r--packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java118
-rw-r--r--packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml2
-rw-r--r--packages/CredentialManager/res/values/strings.xml8
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt8
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt51
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt39
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt76
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt1
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt9
-rw-r--r--packages/PackageInstaller/Android.bp3
-rw-r--r--packages/PackageInstaller/AndroidManifest.xml11
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java173
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java59
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java36
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java327
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java100
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java202
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java621
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java133
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v35/themes.xml24
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/styles.xml28
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes.xml24
-rw-r--r--packages/SettingsLib/FooterPreference/res/layout-v35/preference_footer.xml74
-rw-r--r--packages/SettingsLib/Graph/Android.bp21
-rw-r--r--packages/SettingsLib/Graph/AndroidManifest.xml6
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenManager.kt70
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenProvider.kt26
-rw-r--r--packages/SettingsLib/MainSwitchPreference/res/color-night-v35/settingslib_main_switch_text_color.xml23
-rw-r--r--packages/SettingsLib/MainSwitchPreference/res/color-v35/settingslib_main_switch_text_color.xml23
-rw-r--r--packages/SettingsLib/ProfileSelector/res/color-night-v35/settingslib_tabs_indicator_color.xml19
-rw-r--r--packages/SettingsLib/ProfileSelector/res/color-v35/settingslib_tabs_indicator_color.xml19
-rw-r--r--packages/SettingsLib/SettingsTheme/res/color-night-v31/settingslib_switch_track_on.xml2
-rw-r--r--packages/SettingsLib/SettingsTheme/res/color-night-v35/settingslib_switch_track_outline_color.xml28
-rw-r--r--packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml1
-rw-r--r--packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_switch_track_off.xml2
-rw-r--r--packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml23
-rw-r--r--packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_switch_track_outline_color.xml28
-rw-r--r--packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_primary.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_secondary.xml22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_progress_horizontal.xml38
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml30
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml33
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml (renamed from packages/SettingsLib/FooterPreference/res/drawable-v35/settingslib_ic_info_outline_24.xml)22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml33
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml5
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml3
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml28
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml24
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml1
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v33/themes.xml4
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml4
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml32
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml35
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt7
-rw-r--r--packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java66
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java6
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java3
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java2
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java3
-rw-r--r--packages/Shell/AndroidManifest.xml3
-rw-r--r--packages/SystemUI/Android.bp1
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig20
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffect.kt111
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffectConfig.kt65
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealShader.kt144
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/utils/MathUtils.kt50
-rw-r--r--packages/SystemUI/checks/Android.bp33
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/DumpableNotRegisteredDetector.kt1
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt2
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt20
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt26
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt16
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt3
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt1
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DumpableNotRegisteredDetectorTest.kt13
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt11
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt11
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt33
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt17
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt9
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt7
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt3
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/TestFunctionNameViolationDetectorTest.kt26
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt14
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt6
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt37
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt41
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt7
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt19
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt74
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt35
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt47
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt34
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt1323
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt215
-rw-r--r--packages/SystemUI/res/color/notification_focus_overlay_color.xml (renamed from packages/SettingsLib/SettingsTheme/res/values-v35/styles.xml)12
-rw-r--r--packages/SystemUI/res/drawable/notification_material_bg.xml5
-rw-r--r--packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java54
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java177
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/util/CommunalColors.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java944
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java61
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java51
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt64
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiInputLog.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt114
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt574
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt414
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt66
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerStartable.java74
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt85
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/model/SliderType.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt66
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/Result.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java49
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java193
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicReleaseTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/external/TileColorPickerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt58
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt225
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt253
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt44
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java64
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java102
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt101
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt)6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerStartableTest.java113
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffectTest.kt91
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/NotificationChannelsTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java141
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/CommunalColorsKosmos.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibTableLog.kt)13
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/FakeCommunalColors.kt33
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt2
-rw-r--r--services/accessibility/accessibility.aconfig10
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java23
-rw-r--r--services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java8
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java17
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java10
-rw-r--r--services/autofill/bugfixes.aconfig11
-rw-r--r--services/companion/java/com/android/server/companion/BackupRestoreProcessor.java2
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java2
-rw-r--r--services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java2
-rw-r--r--services/companion/java/com/android/server/companion/association/AssociationStore.java18
-rw-r--r--services/core/java/com/android/server/ExplicitHealthCheckController.java (renamed from packages/CrashRecovery/services/java/com/android/server/ExplicitHealthCheckController.java)0
-rw-r--r--services/core/java/com/android/server/OWNERS5
-rw-r--r--services/core/java/com/android/server/PackageWatchdog.java (renamed from packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java)6
-rw-r--r--services/core/java/com/android/server/RescueParty.java (renamed from packages/CrashRecovery/services/java/com/android/server/RescueParty.java)4
-rw-r--r--services/core/java/com/android/server/SensitiveContentProtectionManagerService.java60
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java5
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java7
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java80
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java8
-rw-r--r--services/core/java/com/android/server/am/AppRestrictionController.java115
-rw-r--r--services/core/java/com/android/server/am/AppStartInfoTracker.java34
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueModernImpl.java4
-rw-r--r--services/core/java/com/android/server/am/ContentProviderHelper.java3
-rw-r--r--services/core/java/com/android/server/am/OomAdjProfiler.java226
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java6
-rw-r--r--services/core/java/com/android/server/am/OomAdjusterModernImpl.java4
-rw-r--r--services/core/java/com/android/server/am/UserController.java89
-rw-r--r--services/core/java/com/android/server/apphibernation/AppHibernationService.java28
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java14
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java10
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java10
-rw-r--r--services/core/java/com/android/server/audio/BtHelper.java6
-rw-r--r--services/core/java/com/android/server/biometrics/biometrics.aconfig10
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java55
-rw-r--r--services/core/java/com/android/server/content/ContentService.java2
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java8
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java69
-rw-r--r--services/core/java/com/android/server/display/ExternalDisplayPolicy.java7
-rw-r--r--services/core/java/com/android/server/display/mode/DisplayModeDirector.java44
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java6
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java8
-rw-r--r--services/core/java/com/android/server/media/SystemMediaRoute2Provider.java4
-rw-r--r--services/core/java/com/android/server/pdb/PersistentDataBlockService.java16
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerService.java25
-rw-r--r--services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java (renamed from packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java)0
-rw-r--r--services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java (renamed from packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java)0
-rw-r--r--services/core/java/com/android/server/security/FileIntegrityService.java18
-rw-r--r--services/core/java/com/android/server/selinux/QuotaLimiter.java10
-rw-r--r--services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java73
-rw-r--r--services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java21
-rw-r--r--services/core/java/com/android/server/selinux/SelinuxAuditLogsJob.java60
-rw-r--r--services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java148
-rw-r--r--services/core/java/com/android/server/stats/pull/StatsPullAtomService.java50
-rw-r--r--services/core/java/com/android/server/stats/stats_flags.aconfig10
-rw-r--r--services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java30
-rw-r--r--services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java12
-rw-r--r--services/core/java/com/android/server/utils/AnrTimer.java20
-rw-r--r--services/core/java/com/android/server/vcn/VcnContext.java14
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorControlService.java41
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java152
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java4
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java36
-rw-r--r--services/core/java/com/android/server/wm/AppWarnings.java428
-rw-r--r--services/core/java/com/android/server/wm/DeprecatedAbiDialog.java6
-rw-r--r--services/core/java/com/android/server/wm/DeprecatedTargetSdkVersionDialog.java6
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java35
-rw-r--r--services/core/java/com/android/server/wm/LetterboxUiController.java9
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java68
-rw-r--r--services/core/java/com/android/server/wm/UnsupportedCompileSdkDialog.java6
-rw-r--r--services/core/java/com/android/server/wm/UnsupportedDisplaySizeDialog.java6
-rw-r--r--services/core/java/com/android/server/wm/WallpaperWindowToken.java10
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java5
-rw-r--r--services/core/jni/OWNERS7
-rw-r--r--services/core/jni/com_android_server_utils_AnrTimer.cpp2
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java102
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java3
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java3
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java31
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java4
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java2
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java13
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java11
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java5
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java14
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java85
-rw-r--r--services/tests/selinux/Android.bp1
-rw-r--r--services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java182
-rw-r--r--services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java161
-rw-r--r--services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsJobTest.java130
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java64
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java24
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/am/UserControllerTest.java208
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java34
-rw-r--r--services/tests/vibrator/utils/com/android/server/vibrator/VibrationParamGenerator.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java73
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskTests.java7
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java2
-rw-r--r--telephony/java/android/telephony/SubscriptionManager.java25
-rw-r--r--telephony/java/com/android/internal/telephony/ISub.aidl12
-rw-r--r--tests/CtsSurfaceControlTestsStaging/Android.bp1
-rw-r--r--tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java4
-rw-r--r--tests/TouchLatency/app/src/main/res/values/styles.xml1
-rw-r--r--tests/WindowInsetsTests/AndroidManifest.xml7
-rw-r--r--tests/WindowInsetsTests/res/layout/controller_activity.xml184
-rw-r--r--tests/WindowInsetsTests/res/layout/main_activity.xml38
-rw-r--r--tests/WindowInsetsTests/res/values-night/styles.xml43
-rw-r--r--tests/WindowInsetsTests/res/values/strings.xml10
-rw-r--r--tests/WindowInsetsTests/res/values/styles.xml13
-rw-r--r--tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java73
-rw-r--r--tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsTestsMainActivity.java18
-rw-r--r--tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java1
-rw-r--r--tools/streaming_proto/java/java_proto_stream_code_generator.cpp84
-rw-r--r--wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java10
486 files changed, 11426 insertions, 7497 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index eac416a37c20..24cd61053c5e 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -641,6 +641,19 @@ java_aconfig_library {
],
}
+java_aconfig_library {
+ name: "android.permission.flags-aconfig-java-host",
+ aconfig_declarations: "android.permission.flags-aconfig",
+ host_supported: true,
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+ min_sdk_version: "30",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.permission",
+ "com.android.nfcservices",
+ ],
+}
+
// SQLite
aconfig_declarations {
name: "android.database.sqlite-aconfig",
diff --git a/Android.bp b/Android.bp
index 4f715f8a3101..d6b303f62428 100644
--- a/Android.bp
+++ b/Android.bp
@@ -97,7 +97,7 @@ filegroup {
// AIDL sources from external directories
":android.frameworks.location.altitude-V2-java-source",
":android.hardware.biometrics.common-V4-java-source",
- ":android.hardware.biometrics.fingerprint-V3-java-source",
+ ":android.hardware.biometrics.fingerprint-V5-java-source",
":android.hardware.biometrics.face-V4-java-source",
":android.hardware.gnss-V2-java-source",
":android.hardware.graphics.common-V3-java-source",
diff --git a/apct-tests/perftests/inputmethod/AndroidManifest.xml b/apct-tests/perftests/inputmethod/AndroidManifest.xml
index 5dd6ccccfb1c..34f9692cc8ed 100644
--- a/apct-tests/perftests/inputmethod/AndroidManifest.xml
+++ b/apct-tests/perftests/inputmethod/AndroidManifest.xml
@@ -22,7 +22,7 @@
<application>
<uses-library android:name="android.test.runner" />
<activity android:name="android.perftests.utils.PerfTestActivity"
- android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
+ android:theme="@android:style/Theme.DeviceDefault.Light.NoActionBar"
android:exported="true">
<intent-filter>
<action android:name="com.android.perftests.core.PERFTEST" />
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index 75e2efd2ec99..e20f525fcdaf 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -28,3 +28,13 @@ flag {
description: "Only relax a prefetch job's connectivity constraint when the device is charging and battery is not low"
bug: "299329948"
}
+
+flag {
+ name: "count_quota_fix"
+ namespace: "backstage_power"
+ description: "Fix job count quota check"
+ bug: "300862949"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 012ede274bc1..3bb395f39123 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -1650,6 +1650,16 @@ class JobConcurrencyManager {
continue;
}
+ if (Flags.countQuotaFix() && !nextPending.isReady()) {
+ // This could happen when the constraints for the job have been marked
+ // as unsatisfiled but hasn't been removed from the pending queue yet.
+ if (DEBUG) {
+ Slog.w(TAG, "Pending+not ready job: " + nextPending);
+ }
+ pendingJobQueue.remove(nextPending);
+ continue;
+ }
+
if (DEBUG && isSimilarJobRunningLocked(nextPending)) {
Slog.w(TAG, "Already running similar job to: " + nextPending);
}
@@ -1737,6 +1747,16 @@ class JobConcurrencyManager {
continue;
}
+ if (Flags.countQuotaFix() && !nextPending.isReady()) {
+ // This could happen when the constraints for the job have been marked
+ // as unsatisfiled but hasn't been removed from the pending queue yet.
+ if (DEBUG) {
+ Slog.w(TAG, "Pending+not ready job: " + nextPending);
+ }
+ pendingJobQueue.remove(nextPending);
+ continue;
+ }
+
if (DEBUG && isSimilarJobRunningLocked(nextPending)) {
Slog.w(TAG, "Already running similar job to: " + nextPending);
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 3c9648b20003..c240b3f423a9 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -70,6 +70,7 @@ import com.android.server.AppSchedulingModuleThread;
import com.android.server.LocalServices;
import com.android.server.PowerAllowlistInternal;
import com.android.server.job.ConstantsProto;
+import com.android.server.job.Flags;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateControllerProto;
import com.android.server.usage.AppStandbyInternal;
@@ -512,7 +513,7 @@ public final class QuotaController extends StateController {
/** An app has reached its quota. The message should contain a {@link UserPackage} object. */
@VisibleForTesting
- static final int MSG_REACHED_QUOTA = 0;
+ static final int MSG_REACHED_TIME_QUOTA = 0;
/** Drop any old timing sessions. */
private static final int MSG_CLEAN_UP_SESSIONS = 1;
/** Check if a package is now within its quota. */
@@ -524,7 +525,7 @@ public final class QuotaController extends StateController {
* object.
*/
@VisibleForTesting
- static final int MSG_REACHED_EJ_QUOTA = 4;
+ static final int MSG_REACHED_EJ_TIME_QUOTA = 4;
/**
* Process a new {@link UsageEvents.Event}. The event will be the message's object and the
* userId will the first arg.
@@ -533,6 +534,11 @@ public final class QuotaController extends StateController {
/** A UID's free quota grace period has ended. */
@VisibleForTesting
static final int MSG_END_GRACE_PERIOD = 6;
+ /**
+ * An app has reached its job count quota. The message should contain a {@link UserPackage}
+ * object.
+ */
+ static final int MSG_REACHED_COUNT_QUOTA = 7;
public QuotaController(@NonNull JobSchedulerService service,
@NonNull BackgroundJobsController backgroundJobsController,
@@ -874,17 +880,46 @@ public final class QuotaController extends StateController {
}
@VisibleForTesting
+ @GuardedBy("mLock")
boolean isWithinQuotaLocked(@NonNull final JobStatus jobStatus) {
final int standbyBucket = jobStatus.getEffectiveStandbyBucket();
// A job is within quota if one of the following is true:
// 1. it was started while the app was in the TOP state
// 2. the app is currently in the foreground
// 3. the app overall is within its quota
- return jobStatus.shouldTreatAsUserInitiatedJob()
+ if (!Flags.countQuotaFix()) {
+ return jobStatus.shouldTreatAsUserInitiatedJob()
+ || isTopStartedJobLocked(jobStatus)
+ || isUidInForeground(jobStatus.getSourceUid())
+ || isWithinQuotaLocked(
+ jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
+ }
+
+ if (jobStatus.shouldTreatAsUserInitiatedJob()
|| isTopStartedJobLocked(jobStatus)
- || isUidInForeground(jobStatus.getSourceUid())
- || isWithinQuotaLocked(
- jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket);
+ || isUidInForeground(jobStatus.getSourceUid())) {
+ return true;
+ }
+
+ if (standbyBucket == NEVER_INDEX) return false;
+
+ if (isQuotaFreeLocked(standbyBucket)) return true;
+
+ final ExecutionStats stats = getExecutionStatsLocked(jobStatus.getSourceUserId(),
+ jobStatus.getSourcePackageName(), standbyBucket);
+ if (!(getRemainingExecutionTimeLocked(stats) > 0)) {
+ // Out of execution time quota.
+ return false;
+ }
+
+ if (standbyBucket != RESTRICTED_INDEX && mService.isCurrentlyRunningLocked(jobStatus)) {
+ // Running job is considered as within quota except for the restricted one, which
+ // requires additional constraints.
+ return true;
+ }
+
+ // Check if the app is within job count quota.
+ return isUnderJobCountQuotaLocked(stats) && isUnderSessionCountQuotaLocked(stats);
}
@GuardedBy("mLock")
@@ -909,12 +944,11 @@ public final class QuotaController extends StateController {
ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
// TODO: use a higher minimum remaining time for jobs with MINIMUM priority
return getRemainingExecutionTimeLocked(stats) > 0
- && isUnderJobCountQuotaLocked(stats, standbyBucket)
- && isUnderSessionCountQuotaLocked(stats, standbyBucket);
+ && isUnderJobCountQuotaLocked(stats)
+ && isUnderSessionCountQuotaLocked(stats);
}
- private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats,
- final int standbyBucket) {
+ private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats) {
final long now = sElapsedRealtimeClock.millis();
final boolean isUnderAllowedTimeQuota =
(stats.jobRateLimitExpirationTimeElapsed <= now
@@ -923,8 +957,7 @@ public final class QuotaController extends StateController {
&& stats.bgJobCountInWindow < stats.jobCountLimit;
}
- private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats,
- final int standbyBucket) {
+ private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats) {
final long now = sElapsedRealtimeClock.millis();
final boolean isUnderAllowedTimeQuota = (stats.sessionRateLimitExpirationTimeElapsed <= now
|| stats.sessionCountInRateLimitingWindow < mMaxSessionCountPerRateLimitingWindow);
@@ -1449,6 +1482,9 @@ public final class QuotaController extends StateController {
stats.jobCountInRateLimitingWindow = 0;
}
stats.jobCountInRateLimitingWindow += count;
+ if (Flags.countQuotaFix()) {
+ stats.bgJobCountInWindow += count;
+ }
}
}
@@ -1683,10 +1719,11 @@ public final class QuotaController extends StateController {
changedJobs.add(js);
}
} else if (realStandbyBucket != EXEMPTED_INDEX && realStandbyBucket != ACTIVE_INDEX
- && realStandbyBucket == js.getEffectiveStandbyBucket()) {
+ && realStandbyBucket == js.getEffectiveStandbyBucket()
+ && !(Flags.countQuotaFix() && mService.isCurrentlyRunningLocked(js))) {
// An app in the ACTIVE bucket may be out of quota while the job could be in quota
// for some reason. Therefore, avoid setting the real value here and check each job
- // individually.
+ // individually. Running job need to determine its own quota status as well.
if (setConstraintSatisfied(js, nowElapsed, realInQuota, isWithinEJQuota)) {
changedJobs.add(js);
}
@@ -1805,9 +1842,8 @@ public final class QuotaController extends StateController {
}
ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
- final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats, standbyBucket);
- final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats,
- standbyBucket);
+ final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats);
+ final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats);
final long remainingEJQuota = getRemainingEJExecutionTimeLocked(userId, packageName);
final boolean inRegularQuota =
@@ -2126,6 +2162,13 @@ public final class QuotaController extends StateController {
mBgJobCount++;
if (mRegularJobTimer) {
incrementJobCountLocked(mPkg.userId, mPkg.packageName, 1);
+ if (Flags.countQuotaFix()) {
+ final ExecutionStats stats = getExecutionStatsLocked(mPkg.userId,
+ mPkg.packageName, jobStatus.getEffectiveStandbyBucket(), false);
+ if (!isUnderJobCountQuotaLocked(stats)) {
+ mHandler.obtainMessage(MSG_REACHED_COUNT_QUOTA, mPkg).sendToTarget();
+ }
+ }
}
if (mRunningBgJobs.size() == 1) {
// Started tracking the first job.
@@ -2257,7 +2300,6 @@ public final class QuotaController extends StateController {
// repeatedly plugged in and unplugged, or an app changes foreground state
// very frequently, the job count for a package may be artificially high.
mBgJobCount = mRunningBgJobs.size();
-
if (mRegularJobTimer) {
incrementJobCountLocked(mPkg.userId, mPkg.packageName, mBgJobCount);
// Starting the timer means that all cached execution stats are now
@@ -2284,7 +2326,8 @@ public final class QuotaController extends StateController {
return;
}
Message msg = mHandler.obtainMessage(
- mRegularJobTimer ? MSG_REACHED_QUOTA : MSG_REACHED_EJ_QUOTA, mPkg);
+ mRegularJobTimer ? MSG_REACHED_TIME_QUOTA : MSG_REACHED_EJ_TIME_QUOTA,
+ mPkg);
final long timeRemainingMs = mRegularJobTimer
? getTimeUntilQuotaConsumedLocked(mPkg.userId, mPkg.packageName)
: getTimeUntilEJQuotaConsumedLocked(mPkg.userId, mPkg.packageName);
@@ -2301,7 +2344,7 @@ public final class QuotaController extends StateController {
private void cancelCutoff() {
mHandler.removeMessages(
- mRegularJobTimer ? MSG_REACHED_QUOTA : MSG_REACHED_EJ_QUOTA, mPkg);
+ mRegularJobTimer ? MSG_REACHED_TIME_QUOTA : MSG_REACHED_EJ_TIME_QUOTA, mPkg);
}
public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
@@ -2557,7 +2600,7 @@ public final class QuotaController extends StateController {
break;
default:
if (DEBUG) {
- Slog.d(TAG, "Dropping event " + event.getEventType());
+ Slog.d(TAG, "Dropping usage event " + event.getEventType());
}
break;
}
@@ -2666,7 +2709,7 @@ public final class QuotaController extends StateController {
public void handleMessage(Message msg) {
synchronized (mLock) {
switch (msg.what) {
- case MSG_REACHED_QUOTA: {
+ case MSG_REACHED_TIME_QUOTA: {
UserPackage pkg = (UserPackage) msg.obj;
if (DEBUG) {
Slog.d(TAG, "Checking if " + pkg + " has reached its quota.");
@@ -2685,7 +2728,7 @@ public final class QuotaController extends StateController {
// This could potentially happen if an old session phases out while a
// job is currently running.
// Reschedule message
- Message rescheduleMsg = obtainMessage(MSG_REACHED_QUOTA, pkg);
+ Message rescheduleMsg = obtainMessage(MSG_REACHED_TIME_QUOTA, pkg);
timeRemainingMs = getTimeUntilQuotaConsumedLocked(pkg.userId,
pkg.packageName);
if (DEBUG) {
@@ -2695,7 +2738,7 @@ public final class QuotaController extends StateController {
}
break;
}
- case MSG_REACHED_EJ_QUOTA: {
+ case MSG_REACHED_EJ_TIME_QUOTA: {
UserPackage pkg = (UserPackage) msg.obj;
if (DEBUG) {
Slog.d(TAG, "Checking if " + pkg + " has reached its EJ quota.");
@@ -2713,7 +2756,7 @@ public final class QuotaController extends StateController {
// This could potentially happen if an old session phases out while a
// job is currently running.
// Reschedule message
- Message rescheduleMsg = obtainMessage(MSG_REACHED_EJ_QUOTA, pkg);
+ Message rescheduleMsg = obtainMessage(MSG_REACHED_EJ_TIME_QUOTA, pkg);
timeRemainingMs = getTimeUntilEJQuotaConsumedLocked(
pkg.userId, pkg.packageName);
if (DEBUG) {
@@ -2723,6 +2766,18 @@ public final class QuotaController extends StateController {
}
break;
}
+ case MSG_REACHED_COUNT_QUOTA: {
+ UserPackage pkg = (UserPackage) msg.obj;
+ if (DEBUG) {
+ Slog.d(TAG, pkg + " has reached its count quota.");
+ }
+
+ mStateChangedListener.onControllerStateChanged(
+ maybeUpdateConstraintForPkgLocked(
+ sElapsedRealtimeClock.millis(),
+ pkg.userId, pkg.packageName));
+ break;
+ }
case MSG_CLEAN_UP_SESSIONS:
if (DEBUG) {
Slog.d(TAG, "Cleaning up timing sessions.");
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 613678bedf8a..410074e6ec85 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -1989,6 +1989,9 @@ public class AppStandbyController
mAdminProtectedPackages.put(userId, packageNames);
}
}
+ if (android.app.admin.flags.Flags.disallowUserControlBgUsageFix()) {
+ postCheckIdleStates(userId);
+ }
}
@Override
diff --git a/config/Android.bp b/config/Android.bp
index 6a6f848b7d52..dd681ca09723 100644
--- a/config/Android.bp
+++ b/config/Android.bp
@@ -33,3 +33,9 @@ filegroup {
name: "preloaded-classes-denylist",
srcs: ["preloaded-classes-denylist"],
}
+
+prebuilt_etc {
+ name: "dirty-image-objects",
+ src: "dirty-image-objects",
+ filename: "dirty-image-objects",
+}
diff --git a/core/api/current.txt b/core/api/current.txt
index c86e4cd40cfa..e2d860ef778f 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -26804,7 +26804,6 @@ package android.media.session {
field public static final int STATE_FAST_FORWARDING = 4; // 0x4
field public static final int STATE_NONE = 0; // 0x0
field public static final int STATE_PAUSED = 2; // 0x2
- field @FlaggedApi("com.android.media.flags.enable_notifying_activity_manager_with_media_session_status_change") public static final int STATE_PLAYBACK_SUPPRESSED = 12; // 0xc
field public static final int STATE_PLAYING = 3; // 0x3
field public static final int STATE_REWINDING = 5; // 0x5
field public static final int STATE_SKIPPING_TO_NEXT = 10; // 0xa
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index bed8c4169a63..45bcd0dbbf21 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1145,7 +1145,6 @@ package android.app {
public static final class StatusBarManager.DisableInfo implements android.os.Parcelable {
method public boolean areAllComponentsEnabled();
method public int describeContents();
- method public boolean isBackDisabled();
method public boolean isNavigateToHomeDisabled();
method public boolean isNotificationPeekingDisabled();
method public boolean isRecentsDisabled();
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 0dab3de599dc..5e9fdfbb6f6f 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1349,12 +1349,26 @@ public class ActivityManager {
public static final int RESTRICTION_LEVEL_BACKGROUND_RESTRICTED = 50;
/**
- * The most restricted level where the apps are considered "in-hibernation",
- * its package visibility to the rest of the system is limited.
+ * The restricted level where the apps are in a force-stopped state.
*
* @hide
*/
- public static final int RESTRICTION_LEVEL_HIBERNATION = 60;
+ public static final int RESTRICTION_LEVEL_FORCE_STOPPED = 60;
+
+ /**
+ * The heavily background restricted level, where apps cannot start without an explicit
+ * launch by the user.
+ *
+ * @hide
+ */
+ public static final int RESTRICTION_LEVEL_USER_LAUNCH_ONLY = 70;
+
+ /**
+ * A reserved restriction level that is not well-defined.
+ *
+ * @hide
+ */
+ public static final int RESTRICTION_LEVEL_CUSTOM = 90;
/**
* Not a valid restriction level, it defines the maximum numerical value of restriction level.
@@ -1371,12 +1385,116 @@ public class ActivityManager {
RESTRICTION_LEVEL_ADAPTIVE_BUCKET,
RESTRICTION_LEVEL_RESTRICTED_BUCKET,
RESTRICTION_LEVEL_BACKGROUND_RESTRICTED,
- RESTRICTION_LEVEL_HIBERNATION,
+ RESTRICTION_LEVEL_FORCE_STOPPED,
+ RESTRICTION_LEVEL_USER_LAUNCH_ONLY,
+ RESTRICTION_LEVEL_CUSTOM,
RESTRICTION_LEVEL_MAX,
})
@Retention(RetentionPolicy.SOURCE)
public @interface RestrictionLevel{}
+ /**
+ * Maximum string length for sub reason for restriction.
+ *
+ * @hide
+ */
+ public static final int RESTRICTION_SUBREASON_MAX_LENGTH = 16;
+
+ /**
+ * Restriction reason unknown - do not use directly.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_UNKNOWN = 0;
+
+ /**
+ * Restriction reason to be used when this is normal behavior for the state.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_DEFAULT = 1;
+
+ /**
+ * Restriction reason is some kind of timeout that moves the app to a more restricted state.
+ * The threshold should specify how long the app was dormant, in milliseconds.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_DORMANT = 2;
+
+ /**
+ * Restriction reason to be used when removing a restriction due to direct or indirect usage
+ * of the app, especially to undo any automatic restrictions.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_USAGE = 3;
+
+ /**
+ * Restriction reason to be used when the user chooses to manually restrict the app, through
+ * UI or command line interface.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_USER = 4;
+
+ /**
+ * Restriction reason to be used when the user chooses to manually restrict the app on being
+ * prompted by the OS or some anomaly detection algorithm. For example, if the app is causing
+ * high battery drain or affecting system performance and the OS recommends that the user
+ * restrict the app.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_USER_NUDGED = 5;
+
+ /**
+ * Restriction reason to be used when the OS automatically detects that the app is causing
+ * system health issues such as performance degradation, battery drain, high memory usage, etc.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_SYSTEM_HEALTH = 6;
+
+ /**
+ * Restriction reason to be used when there is a server-side decision made to restrict an app
+ * that is showing widespread problems on user devices, or violating policy in some way.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_REMOTE_TRIGGER = 7;
+
+ /**
+ * Restriction reason to be used when some other problem requires restricting the app.
+ *
+ * For use with noteAppRestrictionEnabled()
+ * @hide
+ */
+ public static final int RESTRICTION_REASON_OTHER = 8;
+
+ /** @hide */
+ @IntDef(prefix = { "RESTRICTION_REASON_" }, value = {
+ RESTRICTION_REASON_UNKNOWN,
+ RESTRICTION_REASON_DEFAULT,
+ RESTRICTION_REASON_DORMANT,
+ RESTRICTION_REASON_USAGE,
+ RESTRICTION_REASON_USER,
+ RESTRICTION_REASON_USER_NUDGED,
+ RESTRICTION_REASON_SYSTEM_HEALTH,
+ RESTRICTION_REASON_REMOTE_TRIGGER,
+ RESTRICTION_REASON_OTHER
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RestrictionReason{}
+
/** @hide */
@android.ravenwood.annotation.RavenwoodKeep
public static String restrictionLevelToName(@RestrictionLevel int level) {
@@ -1393,12 +1511,16 @@ public class ActivityManager {
return "restricted_bucket";
case RESTRICTION_LEVEL_BACKGROUND_RESTRICTED:
return "background_restricted";
- case RESTRICTION_LEVEL_HIBERNATION:
- return "hibernation";
+ case RESTRICTION_LEVEL_FORCE_STOPPED:
+ return "stopped";
+ case RESTRICTION_LEVEL_USER_LAUNCH_ONLY:
+ return "user_only";
+ case RESTRICTION_LEVEL_CUSTOM:
+ return "custom";
case RESTRICTION_LEVEL_MAX:
return "max";
default:
- return "";
+ return String.valueOf(level);
}
}
@@ -6127,6 +6249,43 @@ public class ActivityManager {
}
/**
+ * Requests the system to log the reason for restricting/unrestricting an app. This API
+ * should be called before applying any change to the restriction level.
+ * <p>
+ * The {@code enabled} value determines whether the state is being applied or removed.
+ * Not all restrictions are actual restrictions. For example,
+ * {@link #RESTRICTION_LEVEL_ADAPTIVE} is a normal state, where there is default lifecycle
+ * management applied to the app. Also, {@link #RESTRICTION_LEVEL_EXEMPTED} is used when the
+ * app is being put in a power-save allowlist.
+ *
+ * @param packageName the package name of the app
+ * @param uid the uid of the app
+ * @param restrictionLevel the restriction level specified in {@code RestrictionLevel}
+ * @param enabled whether the state is being applied or removed
+ * @param reason the reason for the restriction state change, from {@code RestrictionReason}
+ * @param subReason a string sub reason limited to 16 characters that specifies additional
+ * information about the reason for restriction.
+ * @param threshold for reasons that are due to exceeding some threshold, the threshold value
+ * must be specified. The unit of the threshold depends on the reason and/or
+ * subReason. For time, use milliseconds. For memory, use KB. For count, use
+ * the actual count or normalized as per-hour. For power, use milliwatts. Etc.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.DEVICE_POWER)
+ public void noteAppRestrictionEnabled(@NonNull String packageName, int uid,
+ @RestrictionLevel int restrictionLevel, boolean enabled,
+ @RestrictionReason int reason,
+ @Nullable String subReason, long threshold) {
+ try {
+ getService().noteAppRestrictionEnabled(packageName, uid, restrictionLevel, enabled,
+ reason, subReason, threshold);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Notifies {@link #getRunningAppProcesses app processes} that the system properties
* have changed.
*
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index dca164d6acc1..3765c817b117 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -1009,4 +1009,12 @@ interface IActivityManager {
* @param originatingUid The UID of the instrumented app that initialized the override
*/
void clearAllOverridePermissionStates(int originatingUid);
+
+ /**
+ * Request the system to log the reason for restricting / unrestricting an app.
+ * @see ActivityManager#noteAppRestrictionEnabled
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.DEVICE_POWER)")
+ void noteAppRestrictionEnabled(in String packageName, int uid, int restrictionType,
+ boolean enabled, int reason, in String subReason, long threshold);
}
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index e6e46ddaa420..301fef877d6c 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -1448,7 +1448,6 @@ public class StatusBarManager {
*
* @hide
*/
- @SystemApi
public boolean isBackDisabled() {
return mBack;
}
@@ -1862,38 +1861,38 @@ public class StatusBarManager {
};
@DataClass.Generated(
- time = 1707345957771L,
+ time = 1708625947132L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/app/StatusBarManager.java",
- inputSignatures = "private boolean mStatusBarExpansion\nprivate "
- + "boolean mNavigateHome\nprivate boolean mNotificationPeeking\nprivate "
- + "boolean mRecents\nprivate boolean mBack\nprivate boolean "
- + "mSearch\nprivate boolean mSystemIcons\nprivate boolean mClock\nprivate"
- + " boolean mNotificationIcons\nprivate boolean mRotationSuggestion\n"
+ inputSignatures = "private boolean mStatusBarExpansion\nprivate boolean "
+ + "mNavigateHome\nprivate boolean mNotificationPeeking\nprivate "
+ + "boolean mRecents\nprivate boolean mBack\nprivate boolean mSearch\n"
+ + "private boolean mSystemIcons\nprivate boolean mClock\nprivate "
+ + "boolean mNotificationIcons\nprivate boolean mRotationSuggestion\n"
+ "private boolean mNotificationTicker\npublic "
+ "@android.annotation.SystemApi boolean isStatusBarExpansionDisabled()\n"
+ "public void setStatusBarExpansionDisabled(boolean)\npublic "
- + "@android.annotation.SystemApi boolean isNavigateToHomeDisabled()\n"
- + "public void setNavigationHomeDisabled(boolean)\npublic "
- + "@android.annotation.SystemApi boolean isNotificationPeekingDisabled()\n"
- + "public void setNotificationPeekingDisabled(boolean)\npublic "
+ + "@android.annotation.SystemApi boolean isNavigateToHomeDisabled()\npublic"
+ + " void setNavigationHomeDisabled(boolean)\npublic "
+ + "@android.annotation.SystemApi boolean isNotificationPeekingDisabled()"
+ + "\npublic void setNotificationPeekingDisabled(boolean)\npublic "
+ "@android.annotation.SystemApi boolean isRecentsDisabled()\npublic "
- + "void setRecentsDisabled(boolean)\npublic @android.annotation.SystemApi "
- + "boolean isBackDisabled()\npublic void setBackDisabled(boolean)\npublic "
+ + "void setRecentsDisabled(boolean)\npublic boolean isBackDisabled()"
+ + "\npublic void setBackDisabled(boolean)\npublic "
+ "@android.annotation.SystemApi boolean isSearchDisabled()\npublic "
+ "void setSearchDisabled(boolean)\npublic boolean "
- + "areSystemIconsDisabled()\npublic void setSystemIconsDisabled(boolean)"
- + "\npublic boolean isClockDisabled()\npublic "
- + "void setClockDisabled(boolean)\npublic "
- + "boolean areNotificationIconsDisabled()\npublic "
- + "void setNotificationIconsDisabled(boolean)\npublic boolean "
+ + "areSystemIconsDisabled()\npublic void setSystemIconsDisabled(boolean)\n"
+ + "public boolean isClockDisabled()\npublic "
+ + "void setClockDisabled(boolean)\npublic boolean "
+ + "areNotificationIconsDisabled()\npublic void "
+ + "setNotificationIconsDisabled(boolean)\npublic boolean "
+ "isNotificationTickerDisabled()\npublic void "
+ "setNotificationTickerDisabled(boolean)\npublic "
+ "@android.annotation.TestApi boolean isRotationSuggestionDisabled()\n"
+ "public void setRotationSuggestionDisabled(boolean)\npublic "
- + "@android.annotation.SystemApi boolean areAllComponentsEnabled()\n"
- + "public void setEnableAll()\npublic boolean areAllComponentsDisabled()"
- + "\npublic void setDisableAll()\npublic @android.annotation.NonNull "
+ + "@android.annotation.SystemApi boolean areAllComponentsEnabled()\npublic"
+ + " void setEnableAll()\npublic boolean areAllComponentsDisabled()\n"
+ + "public void setDisableAll()\npublic @android.annotation.NonNull "
+ "@java.lang.Override java.lang.String toString()\npublic "
+ "android.util.Pair<java.lang.Integer,java.lang.Integer> toFlags()\n"
+ "class DisableInfo extends java.lang.Object implements "
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 1aee9fe57466..a9f2d74eae39 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -317,11 +317,6 @@ public abstract class DevicePolicyManagerInternal {
public abstract boolean isUserOrganizationManaged(@UserIdInt int userId);
/**
- * Returns whether the application exemptions feature flag is enabled.
- */
- public abstract boolean isApplicationExemptionsFlagEnabled();
-
- /**
* Returns a map of admin to {@link Bundle} map of restrictions set by the admins for the
* provided {@code packageName} in the provided {@code userId}
*/
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 6a07484eebc6..ac843cbfbfac 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -195,6 +195,25 @@ flag {
}
}
+flag {
+ name: "power_exemption_bg_usage_fix"
+ namespace: "enterprise"
+ description: "Ensure aps with EXEMPT_FROM_POWER_RESTRICTIONS can execute in the background"
+ bug: "333379020"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "disallow_user_control_bg_usage_fix"
+ namespace: "enterprise"
+ description: "Make DPM.setUserControlDisabledPackages() ensure background usage is allowed"
+ bug: "326031059"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
flag {
name: "esim_management_ux_enabled"
@@ -228,6 +247,16 @@ flag {
}
flag {
+ name: "always_persist_do"
+ namespace: "enterprise"
+ description: "Always write device_owners2.xml so that migration flags aren't lost"
+ bug: "335232744"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "is_recursive_required_app_merging_enabled"
namespace: "enterprise"
description: "Guards a new flow for recursive required enterprise app list merging"
diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
index 722d5f0fe462..c9b4aa1456fd 100644
--- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java
+++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
@@ -23,6 +23,8 @@ import static com.android.window.flags.Flags.bundleClientTransactionFlag;
import static java.util.Objects.requireNonNull;
+import android.annotation.AnyThread;
+import android.annotation.MainThread;
import android.annotation.NonNull;
import android.app.Activity;
import android.app.ActivityThread;
@@ -94,6 +96,7 @@ public class ClientTransactionListenerController {
* The listener will be invoked with two parameters: {@link Activity#getActivityToken()} and
* {@link ActivityWindowInfo}.
*/
+ @AnyThread
public void registerActivityWindowInfoChangedListener(
@NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) {
if (!activityWindowInfoFlag()) {
@@ -108,6 +111,7 @@ public class ClientTransactionListenerController {
* Unregisters the listener that was previously registered via
* {@link #registerActivityWindowInfoChangedListener(BiConsumer)}
*/
+ @AnyThread
public void unregisterActivityWindowInfoChangedListener(
@NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) {
if (!activityWindowInfoFlag()) {
@@ -122,6 +126,7 @@ public class ClientTransactionListenerController {
* Called when receives a {@link ClientTransaction} that is updating an activity's
* {@link ActivityWindowInfo}.
*/
+ @MainThread
public void onActivityWindowInfoChanged(@NonNull IBinder activityToken,
@NonNull ActivityWindowInfo activityWindowInfo) {
if (!activityWindowInfoFlag()) {
@@ -141,17 +146,20 @@ public class ClientTransactionListenerController {
}
/** Called when starts executing a remote {@link ClientTransaction}. */
+ @MainThread
public void onClientTransactionStarted() {
mIsClientTransactionExecuting = true;
}
/** Called when finishes executing a remote {@link ClientTransaction}. */
+ @MainThread
public void onClientTransactionFinished() {
notifyDisplayManagerIfNeeded();
mIsClientTransactionExecuting = false;
}
/** Called before updating the Configuration of the given {@code context}. */
+ @MainThread
public void onContextConfigurationPreChanged(@NonNull Context context) {
if (!bundleClientTransactionFlag() || ActivityThread.isSystem()) {
// Not enable for system server.
@@ -166,6 +174,7 @@ public class ClientTransactionListenerController {
}
/** Called after updating the Configuration of the given {@code context}. */
+ @MainThread
public void onContextConfigurationPostChanged(@NonNull Context context) {
if (!bundleClientTransactionFlag() || ActivityThread.isSystem()) {
// Not enable for system server.
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index f5bff9dc1d0e..4c0da7c98488 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4844,16 +4844,6 @@ public abstract class PackageManager {
public static final String FEATURE_ROTARY_ENCODER_LOW_RES =
"android.hardware.rotaryencoder.lowres";
- /**
- * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
- * support for contextual search helper.
- *
- * @hide
- */
- @SdkConstant(SdkConstantType.FEATURE)
- public static final String FEATURE_CONTEXTUAL_SEARCH_HELPER =
- "android.software.contextualsearch";
-
/** @hide */
public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true;
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 4963a4f27803..321e539816b2 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -171,6 +171,13 @@ flag {
}
flag {
+ name: "schedule_stop_of_background_user"
+ namespace: "multiuser"
+ description: "Schedule background users to be stopped at a future point."
+ bug: "330351042"
+}
+
+flag {
name: "disable_private_space_items_on_home"
namespace: "profile_experiences"
description: "Disables adding items belonging to Private Space on Home Screen manually as well as automatically"
diff --git a/core/java/android/hardware/OverlayProperties.java b/core/java/android/hardware/OverlayProperties.java
index 4a4d451eb2f6..7b452a8e0857 100644
--- a/core/java/android/hardware/OverlayProperties.java
+++ b/core/java/android/hardware/OverlayProperties.java
@@ -74,7 +74,7 @@ public final class OverlayProperties implements Parcelable {
* and {@link HardwareBuffer.Format} is supported on the device.
*
* @return True if the device can support efficiently compositing the content described by the
- * dataspace and format. False if GPOU composition fallback is otherwise required.
+ * dataspace and format. False if GPU composition fallback is otherwise required.
*/
@FlaggedApi(Flags.FLAG_OVERLAYPROPERTIES_CLASS_API)
public boolean isCombinationSupported(@DataSpace.ColorDataSpace int dataspace,
@@ -135,7 +135,6 @@ public final class OverlayProperties implements Parcelable {
private static native long nGetDestructor();
private static native long nCreateDefault();
- private static native boolean nSupportFp16ForHdr(long nativeObject);
private static native boolean nSupportMixedColorSpaces(long nativeObject);
private static native boolean nIsCombinationSupported(
long nativeObject, int dataspace, int format);
diff --git a/core/java/android/hardware/face/FaceCallback.java b/core/java/android/hardware/face/FaceCallback.java
new file mode 100644
index 000000000000..b69024fd05fa
--- /dev/null
+++ b/core/java/android/hardware/face/FaceCallback.java
@@ -0,0 +1,321 @@
+/*
+ * 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 android.hardware.face;
+
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_VENDOR;
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_VENDOR_BASE;
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_VENDOR;
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_VENDOR_BASE;
+import static android.hardware.face.FaceManager.getAuthHelpMessage;
+import static android.hardware.face.FaceManager.getEnrollHelpMessage;
+import static android.hardware.face.FaceManager.getErrorString;
+
+import android.content.Context;
+import android.hardware.biometrics.CryptoObject;
+import android.hardware.face.FaceManager.AuthenticationCallback;
+import android.hardware.face.FaceManager.EnrollmentCallback;
+import android.hardware.face.FaceManager.FaceDetectionCallback;
+import android.hardware.face.FaceManager.GenerateChallengeCallback;
+import android.hardware.face.FaceManager.GetFeatureCallback;
+import android.hardware.face.FaceManager.RemovalCallback;
+import android.hardware.face.FaceManager.SetFeatureCallback;
+import android.util.Slog;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * Encapsulates callbacks and client specific information for each face related request.
+ * @hide
+ */
+public class FaceCallback {
+ private static final String TAG = " FaceCallback";
+
+ @Nullable
+ private AuthenticationCallback mAuthenticationCallback;
+ @Nullable
+ private EnrollmentCallback mEnrollmentCallback;
+ @Nullable
+ private RemovalCallback mRemovalCallback;
+ @Nullable
+ private GenerateChallengeCallback mGenerateChallengeCallback;
+ @Nullable
+ private FaceDetectionCallback mFaceDetectionCallback;
+ @Nullable
+ private SetFeatureCallback mSetFeatureCallback;
+ @Nullable
+ private GetFeatureCallback mGetFeatureCallback;
+ @Nullable
+ private Face mRemovalFace;
+ @Nullable
+ private CryptoObject mCryptoObject;
+
+ /**
+ * Construction for face authentication client callback.
+ */
+ FaceCallback(AuthenticationCallback authenticationCallback, CryptoObject cryptoObject) {
+ mAuthenticationCallback = authenticationCallback;
+ mCryptoObject = cryptoObject;
+ }
+
+ /**
+ * Construction for face detect client callback.
+ */
+ FaceCallback(FaceDetectionCallback faceDetectionCallback) {
+ mFaceDetectionCallback = faceDetectionCallback;
+ }
+
+ /**
+ * Construction for face enroll client callback.
+ */
+ FaceCallback(EnrollmentCallback enrollmentCallback) {
+ mEnrollmentCallback = enrollmentCallback;
+ }
+
+ /**
+ * Construction for face generate challenge client callback.
+ */
+ FaceCallback(GenerateChallengeCallback generateChallengeCallback) {
+ mGenerateChallengeCallback = generateChallengeCallback;
+ }
+
+ /**
+ * Construction for face set feature client callback.
+ */
+ FaceCallback(SetFeatureCallback setFeatureCallback) {
+ mSetFeatureCallback = setFeatureCallback;
+ }
+
+ /**
+ * Construction for face get feature client callback.
+ */
+ FaceCallback(GetFeatureCallback getFeatureCallback) {
+ mGetFeatureCallback = getFeatureCallback;
+ }
+
+ /**
+ * Construction for single face removal client callback.
+ */
+ FaceCallback(RemovalCallback removalCallback, Face removalFace) {
+ mRemovalCallback = removalCallback;
+ mRemovalFace = removalFace;
+ }
+
+ /**
+ * Construction for all face removal client callback.
+ */
+ FaceCallback(RemovalCallback removalCallback) {
+ mRemovalCallback = removalCallback;
+ }
+
+ /**
+ * Propagate set feature completed via the callback.
+ * @param success if the operation was completed successfully
+ * @param feature the feature that was set
+ */
+ public void sendSetFeatureCompleted(boolean success, int feature) {
+ if (mSetFeatureCallback == null) {
+ return;
+ }
+ mSetFeatureCallback.onCompleted(success, feature);
+ }
+
+ /**
+ * Propagate get feature completed via the callback.
+ * @param success if the operation was completed successfully
+ * @param features list of features available
+ * @param featureState status of the features corresponding to the previous parameter
+ */
+ public void sendGetFeatureCompleted(boolean success, int[] features, boolean[] featureState) {
+ if (mGetFeatureCallback == null) {
+ return;
+ }
+ mGetFeatureCallback.onCompleted(success, features, featureState);
+ }
+
+ /**
+ * Propagate challenge generated completed via the callback.
+ * @param sensorId id of the corresponding sensor
+ * @param userId id of the corresponding sensor
+ * @param challenge value of the challenge generated
+ */
+ public void sendChallengeGenerated(int sensorId, int userId, long challenge) {
+ if (mGenerateChallengeCallback == null) {
+ return;
+ }
+ mGenerateChallengeCallback.onGenerateChallengeResult(sensorId, userId, challenge);
+ }
+
+ /**
+ * Propagate face detected completed via the callback.
+ * @param sensorId id of the corresponding sensor
+ * @param userId id of the corresponding user
+ * @param isStrongBiometric if the sensor is strong or not
+ */
+ public void sendFaceDetected(int sensorId, int userId, boolean isStrongBiometric) {
+ if (mFaceDetectionCallback == null) {
+ Slog.e(TAG, "sendFaceDetected, callback null");
+ return;
+ }
+ mFaceDetectionCallback.onFaceDetected(sensorId, userId, isStrongBiometric);
+ }
+
+ /**
+ * Propagate remove face completed via the callback.
+ * @param face removed identifier
+ * @param remaining number of face enrollments remaining
+ */
+ public void sendRemovedResult(Face face, int remaining) {
+ if (mRemovalCallback == null) {
+ return;
+ }
+ mRemovalCallback.onRemovalSucceeded(face, remaining);
+ }
+
+ /**
+ * Propagate errors via the callback.
+ * @param context corresponding context
+ * @param errMsgId represents the framework error id
+ * @param vendorCode represents the vendor error code
+ */
+ public void sendErrorResult(Context context, int errMsgId, int vendorCode) {
+ // emulate HAL 2.1 behavior and send real errMsgId
+ final int clientErrMsgId = errMsgId == FACE_ERROR_VENDOR
+ ? (vendorCode + FACE_ERROR_VENDOR_BASE) : errMsgId;
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onEnrollmentError(clientErrMsgId,
+ getErrorString(context, errMsgId, vendorCode));
+ } else if (mAuthenticationCallback != null) {
+ mAuthenticationCallback.onAuthenticationError(clientErrMsgId,
+ getErrorString(context, errMsgId, vendorCode));
+ } else if (mRemovalCallback != null) {
+ mRemovalCallback.onRemovalError(mRemovalFace, clientErrMsgId,
+ getErrorString(context, errMsgId, vendorCode));
+ } else if (mFaceDetectionCallback != null) {
+ mFaceDetectionCallback.onDetectionError(errMsgId);
+ mFaceDetectionCallback = null;
+ }
+ }
+
+ /**
+ * Propagate enroll progress via the callback.
+ * @param remaining number of enrollment steps remaining
+ */
+ public void sendEnrollResult(int remaining) {
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onEnrollmentProgress(remaining);
+ }
+ }
+
+ /**
+ * Propagate authentication succeeded via the callback.
+ * @param face matched identifier
+ * @param userId id of the corresponding user
+ * @param isStrongBiometric if the sensor is strong or not
+ */
+ public void sendAuthenticatedSucceeded(Face face, int userId, boolean isStrongBiometric) {
+ if (mAuthenticationCallback != null) {
+ final FaceManager.AuthenticationResult result = new FaceManager.AuthenticationResult(
+ mCryptoObject, face, userId, isStrongBiometric);
+ mAuthenticationCallback.onAuthenticationSucceeded(result);
+ }
+ }
+
+ /**
+ * Propagate authentication failed via the callback.
+ */
+ public void sendAuthenticatedFailed() {
+ if (mAuthenticationCallback != null) {
+ mAuthenticationCallback.onAuthenticationFailed();
+ }
+ }
+
+ /**
+ * Propagate acquired result via the callback.
+ * @param context corresponding context
+ * @param acquireInfo represents the framework acquired id
+ * @param vendorCode represents the vendor acquired code
+ */
+ public void sendAcquiredResult(Context context, int acquireInfo, int vendorCode) {
+ if (mAuthenticationCallback != null) {
+ final FaceAuthenticationFrame frame = new FaceAuthenticationFrame(
+ new FaceDataFrame(acquireInfo, vendorCode));
+ sendAuthenticationFrame(context, frame);
+ } else if (mEnrollmentCallback != null) {
+ final FaceEnrollFrame frame = new FaceEnrollFrame(
+ null /* cell */,
+ FaceEnrollStages.UNKNOWN,
+ new FaceDataFrame(acquireInfo, vendorCode));
+ sendEnrollmentFrame(context, frame);
+ }
+ }
+
+ /**
+ * Propagate authentication frame via the callback.
+ * @param context corresponding context
+ * @param frame authentication frame to be sent
+ */
+ public void sendAuthenticationFrame(@NonNull Context context,
+ @Nullable FaceAuthenticationFrame frame) {
+ if (frame == null) {
+ Slog.w(TAG, "Received null authentication frame");
+ } else if (mAuthenticationCallback != null) {
+ // TODO(b/178414967): Send additional frame data to callback
+ final int acquireInfo = frame.getData().getAcquiredInfo();
+ final int vendorCode = frame.getData().getVendorCode();
+ final int helpCode = getHelpCode(acquireInfo, vendorCode);
+ final String helpMessage = getAuthHelpMessage(context, acquireInfo, vendorCode);
+ mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
+
+ // Ensure that only non-null help messages are sent.
+ if (helpMessage != null) {
+ mAuthenticationCallback.onAuthenticationHelp(helpCode, helpMessage);
+ }
+ }
+ }
+
+ /**
+ * Propagate enrollment via the callback.
+ * @param context corresponding context
+ * @param frame enrollment frame to be sent
+ */
+ public void sendEnrollmentFrame(Context context, @Nullable FaceEnrollFrame frame) {
+ if (frame == null) {
+ Slog.w(TAG, "Received null enrollment frame");
+ } else if (mEnrollmentCallback != null) {
+ final FaceDataFrame data = frame.getData();
+ final int acquireInfo = data.getAcquiredInfo();
+ final int vendorCode = data.getVendorCode();
+ final int helpCode = getHelpCode(acquireInfo, vendorCode);
+ final String helpMessage = getEnrollHelpMessage(context, acquireInfo, vendorCode);
+ mEnrollmentCallback.onEnrollmentFrame(
+ helpCode,
+ helpMessage,
+ frame.getCell(),
+ frame.getStage(),
+ data.getPan(),
+ data.getTilt(),
+ data.getDistance());
+ }
+ }
+
+ private static int getHelpCode(int acquireInfo, int vendorCode) {
+ return acquireInfo == FACE_ACQUIRED_VENDOR
+ ? vendorCode + FACE_ACQUIRED_VENDOR_BASE
+ : acquireInfo;
+ }
+}
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 210ce2b78fca..2592630c80d2 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -37,9 +37,9 @@ import android.os.Binder;
import android.os.CancellationSignal;
import android.os.CancellationSignal.OnCancelListener;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.IRemoteCallback;
-import android.os.Looper;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.Trace;
@@ -49,7 +49,6 @@ import android.util.Slog;
import android.view.Surface;
import com.android.internal.R;
-import com.android.internal.os.SomeArgs;
import java.util.ArrayList;
import java.util.List;
@@ -63,71 +62,56 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
private static final String TAG = "FaceManager";
- private static final int MSG_ENROLL_RESULT = 100;
- private static final int MSG_ACQUIRED = 101;
- private static final int MSG_AUTHENTICATION_SUCCEEDED = 102;
- private static final int MSG_AUTHENTICATION_FAILED = 103;
- private static final int MSG_ERROR = 104;
- private static final int MSG_REMOVED = 105;
- private static final int MSG_GET_FEATURE_COMPLETED = 106;
- private static final int MSG_SET_FEATURE_COMPLETED = 107;
- private static final int MSG_CHALLENGE_GENERATED = 108;
- private static final int MSG_FACE_DETECTED = 109;
- private static final int MSG_AUTHENTICATION_FRAME = 112;
- private static final int MSG_ENROLLMENT_FRAME = 113;
-
private final IFaceService mService;
private final Context mContext;
private final IBinder mToken = new Binder();
- @Nullable private AuthenticationCallback mAuthenticationCallback;
- @Nullable private FaceDetectionCallback mFaceDetectionCallback;
- @Nullable private EnrollmentCallback mEnrollmentCallback;
- @Nullable private RemovalCallback mRemovalCallback;
- @Nullable private SetFeatureCallback mSetFeatureCallback;
- @Nullable private GetFeatureCallback mGetFeatureCallback;
- @Nullable private GenerateChallengeCallback mGenerateChallengeCallback;
- private CryptoObject mCryptoObject;
- private Face mRemovalFace;
private Handler mHandler;
private List<FaceSensorPropertiesInternal> mProps = new ArrayList<>();
+ private HandlerExecutor mExecutor;
+
+ private class FaceServiceReceiver extends IFaceServiceReceiver.Stub {
+ private final FaceCallback mFaceCallback;
- private final IFaceServiceReceiver mServiceReceiver = new IFaceServiceReceiver.Stub() {
+ FaceServiceReceiver(FaceCallback faceCallback) {
+ mFaceCallback = faceCallback;
+ }
@Override // binder call
public void onEnrollResult(Face face, int remaining) {
- mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0, face).sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendEnrollResult(remaining));
}
@Override // binder call
public void onAcquired(int acquireInfo, int vendorCode) {
- mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode).sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendAcquiredResult(mContext, acquireInfo,
+ vendorCode));
}
@Override // binder call
public void onAuthenticationSucceeded(Face face, int userId, boolean isStrongBiometric) {
- mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId,
- isStrongBiometric ? 1 : 0, face).sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendAuthenticatedSucceeded(face, userId,
+ isStrongBiometric));
}
@Override // binder call
public void onFaceDetected(int sensorId, int userId, boolean isStrongBiometric) {
- mHandler.obtainMessage(MSG_FACE_DETECTED, sensorId, userId, isStrongBiometric)
- .sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendFaceDetected(sensorId, userId,
+ isStrongBiometric));
}
@Override // binder call
public void onAuthenticationFailed() {
- mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();
+ mExecutor.execute(mFaceCallback::sendAuthenticatedFailed);
}
@Override // binder call
public void onError(int error, int vendorCode) {
- mHandler.obtainMessage(MSG_ERROR, error, vendorCode).sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendErrorResult(mContext, error, vendorCode));
}
@Override // binder call
public void onRemoved(Face face, int remaining) {
- mHandler.obtainMessage(MSG_REMOVED, remaining, 0, face).sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendRemovedResult(face, remaining));
if (remaining == 0) {
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0,
@@ -137,34 +121,31 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
@Override
public void onFeatureSet(boolean success, int feature) {
- mHandler.obtainMessage(MSG_SET_FEATURE_COMPLETED, feature, 0, success).sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendSetFeatureCompleted(success, feature));
}
@Override
public void onFeatureGet(boolean success, int[] features, boolean[] featureState) {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = success;
- args.arg2 = features;
- args.arg3 = featureState;
- mHandler.obtainMessage(MSG_GET_FEATURE_COMPLETED, args).sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendGetFeatureCompleted(success, features,
+ featureState));
}
@Override
public void onChallengeGenerated(int sensorId, int userId, long challenge) {
- mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, sensorId, userId, challenge)
- .sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendChallengeGenerated(sensorId, userId,
+ challenge));
}
@Override
public void onAuthenticationFrame(FaceAuthenticationFrame frame) {
- mHandler.obtainMessage(MSG_AUTHENTICATION_FRAME, frame).sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendAuthenticationFrame(mContext, frame));
}
@Override
public void onEnrollmentFrame(FaceEnrollFrame frame) {
- mHandler.obtainMessage(MSG_ENROLLMENT_FRAME, frame).sendToTarget();
+ mExecutor.execute(() -> mFaceCallback.sendEnrollmentFrame(mContext, frame));
}
- };
+ }
/**
* @hide
@@ -175,7 +156,8 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
if (mService == null) {
Slog.v(TAG, "FaceAuthenticationManagerService was null");
}
- mHandler = new MyHandler(context);
+ mHandler = context.getMainThreadHandler();
+ mExecutor = new HandlerExecutor(mHandler);
if (context.checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL)
== PackageManager.PERMISSION_GRANTED) {
addAuthenticatorsRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
@@ -193,9 +175,11 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
*/
private void useHandler(Handler handler) {
if (handler != null) {
- mHandler = new MyHandler(handler.getLooper());
- } else if (mHandler.getLooper() != mContext.getMainLooper()) {
- mHandler = new MyHandler(mContext.getMainLooper());
+ mHandler = handler;
+ mExecutor = new HandlerExecutor(mHandler);
+ } else if (mHandler != mContext.getMainThreadHandler()) {
+ mHandler = mContext.getMainThreadHandler();
+ mExecutor = new HandlerExecutor(mHandler);
}
}
@@ -249,13 +233,12 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
if (mService != null) {
try {
+ final FaceCallback faceCallback = new FaceCallback(callback, crypto);
useHandler(handler);
- mAuthenticationCallback = callback;
- mCryptoObject = crypto;
final long operationId = crypto != null ? crypto.getOpId() : 0;
Trace.beginSection("FaceManager#authenticate");
final long authId = mService.authenticate(
- mToken, operationId, mServiceReceiver, options);
+ mToken, operationId, new FaceServiceReceiver(faceCallback), options);
if (cancel != null) {
cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
}
@@ -292,10 +275,11 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
options.setOpPackageName(mContext.getOpPackageName());
options.setAttributionTag(mContext.getAttributionTag());
- mFaceDetectionCallback = callback;
+ final FaceCallback faceCallback = new FaceCallback(callback);
try {
- final long authId = mService.detectFace(mToken, mServiceReceiver, options);
+ final long authId = mService.detectFace(mToken,
+ new FaceServiceReceiver(faceCallback), options);
cancel.setOnCancelListener(new OnFaceDetectionCancelListener(authId));
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception when requesting finger detect", e);
@@ -367,11 +351,11 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
if (mService != null) {
try {
- mEnrollmentCallback = callback;
+ final FaceCallback faceCallback = new FaceCallback(callback);
Trace.beginSection("FaceManager#enroll");
final long enrollId = mService.enroll(userId, mToken, hardwareAuthToken,
- mServiceReceiver, mContext.getOpPackageName(), disabledFeatures,
- previewSurface, debugConsent, options);
+ new FaceServiceReceiver(faceCallback), mContext.getOpPackageName(),
+ disabledFeatures, previewSurface, debugConsent, options);
if (cancel != null) {
cancel.setOnCancelListener(new OnEnrollCancelListener(enrollId));
}
@@ -419,10 +403,11 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
if (mService != null) {
try {
- mEnrollmentCallback = callback;
+ final FaceCallback faceCallback = new FaceCallback(callback);
Trace.beginSection("FaceManager#enrollRemotely");
final long enrolId = mService.enrollRemotely(userId, mToken, hardwareAuthToken,
- mServiceReceiver, mContext.getOpPackageName(), disabledFeatures);
+ new FaceServiceReceiver(faceCallback), mContext.getOpPackageName(),
+ disabledFeatures);
if (cancel != null) {
cancel.setOnCancelListener(new OnEnrollCancelListener(enrolId));
}
@@ -455,9 +440,9 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
public void generateChallenge(int sensorId, int userId, GenerateChallengeCallback callback) {
if (mService != null) {
try {
- mGenerateChallengeCallback = callback;
- mService.generateChallenge(mToken, sensorId, userId, mServiceReceiver,
- mContext.getOpPackageName());
+ final FaceCallback faceCallback = new FaceCallback(callback);
+ mService.generateChallenge(mToken, sensorId, userId,
+ new FaceServiceReceiver(faceCallback), mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -528,9 +513,9 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
SetFeatureCallback callback) {
if (mService != null) {
try {
- mSetFeatureCallback = callback;
+ final FaceCallback faceCallback = new FaceCallback(callback);
mService.setFeature(mToken, userId, feature, enabled, hardwareAuthToken,
- mServiceReceiver, mContext.getOpPackageName());
+ new FaceServiceReceiver(faceCallback), mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -544,8 +529,8 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
public void getFeature(int userId, int feature, GetFeatureCallback callback) {
if (mService != null) {
try {
- mGetFeatureCallback = callback;
- mService.getFeature(mToken, userId, feature, mServiceReceiver,
+ final FaceCallback faceCallback = new FaceCallback(callback);
+ mService.getFeature(mToken, userId, feature, new FaceServiceReceiver(faceCallback),
mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -566,10 +551,9 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
public void remove(Face face, int userId, RemovalCallback callback) {
if (mService != null) {
try {
- mRemovalCallback = callback;
- mRemovalFace = face;
- mService.remove(mToken, face.getBiometricId(), userId, mServiceReceiver,
- mContext.getOpPackageName());
+ final FaceCallback faceCallback = new FaceCallback(callback, face);
+ mService.remove(mToken, face.getBiometricId(), userId,
+ new FaceServiceReceiver(faceCallback), mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -584,8 +568,9 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
public void removeAll(int userId, @NonNull RemovalCallback callback) {
if (mService != null) {
try {
- mRemovalCallback = callback;
- mService.removeAll(mToken, userId, mServiceReceiver, mContext.getOpPackageName());
+ final FaceCallback faceCallback = new FaceCallback(callback);
+ mService.removeAll(mToken, userId, new FaceServiceReceiver(faceCallback),
+ mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1270,203 +1255,6 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
}
}
- private class MyHandler extends Handler {
- private MyHandler(Context context) {
- super(context.getMainLooper());
- }
-
- private MyHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(android.os.Message msg) {
- Trace.beginSection("FaceManager#handleMessage: " + Integer.toString(msg.what));
- switch (msg.what) {
- case MSG_ENROLL_RESULT:
- sendEnrollResult((Face) msg.obj, msg.arg1 /* remaining */);
- break;
- case MSG_ACQUIRED:
- sendAcquiredResult(msg.arg1 /* acquire info */, msg.arg2 /* vendorCode */);
- break;
- case MSG_AUTHENTICATION_SUCCEEDED:
- sendAuthenticatedSucceeded((Face) msg.obj, msg.arg1 /* userId */,
- msg.arg2 == 1 /* isStrongBiometric */);
- break;
- case MSG_AUTHENTICATION_FAILED:
- sendAuthenticatedFailed();
- break;
- case MSG_ERROR:
- sendErrorResult(msg.arg1 /* errMsgId */, msg.arg2 /* vendorCode */);
- break;
- case MSG_REMOVED:
- sendRemovedResult((Face) msg.obj, msg.arg1 /* remaining */);
- break;
- case MSG_SET_FEATURE_COMPLETED:
- sendSetFeatureCompleted((boolean) msg.obj /* success */,
- msg.arg1 /* feature */);
- break;
- case MSG_GET_FEATURE_COMPLETED:
- SomeArgs args = (SomeArgs) msg.obj;
- sendGetFeatureCompleted((boolean) args.arg1 /* success */,
- (int[]) args.arg2 /* features */,
- (boolean[]) args.arg3 /* featureState */);
- args.recycle();
- break;
- case MSG_CHALLENGE_GENERATED:
- sendChallengeGenerated(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
- (long) msg.obj /* challenge */);
- break;
- case MSG_FACE_DETECTED:
- sendFaceDetected(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
- (boolean) msg.obj /* isStrongBiometric */);
- break;
- case MSG_AUTHENTICATION_FRAME:
- sendAuthenticationFrame((FaceAuthenticationFrame) msg.obj /* frame */);
- break;
- case MSG_ENROLLMENT_FRAME:
- sendEnrollmentFrame((FaceEnrollFrame) msg.obj /* frame */);
- break;
- default:
- Slog.w(TAG, "Unknown message: " + msg.what);
- }
- Trace.endSection();
- }
- }
-
- private void sendSetFeatureCompleted(boolean success, int feature) {
- if (mSetFeatureCallback == null) {
- return;
- }
- mSetFeatureCallback.onCompleted(success, feature);
- }
-
- private void sendGetFeatureCompleted(boolean success, int[] features, boolean[] featureState) {
- if (mGetFeatureCallback == null) {
- return;
- }
- mGetFeatureCallback.onCompleted(success, features, featureState);
- }
-
- private void sendChallengeGenerated(int sensorId, int userId, long challenge) {
- if (mGenerateChallengeCallback == null) {
- return;
- }
- mGenerateChallengeCallback.onGenerateChallengeResult(sensorId, userId, challenge);
- }
-
- private void sendFaceDetected(int sensorId, int userId, boolean isStrongBiometric) {
- if (mFaceDetectionCallback == null) {
- Slog.e(TAG, "sendFaceDetected, callback null");
- return;
- }
- mFaceDetectionCallback.onFaceDetected(sensorId, userId, isStrongBiometric);
- }
-
- private void sendRemovedResult(Face face, int remaining) {
- if (mRemovalCallback == null) {
- return;
- }
- mRemovalCallback.onRemovalSucceeded(face, remaining);
- }
-
- private void sendErrorResult(int errMsgId, int vendorCode) {
- // emulate HAL 2.1 behavior and send real errMsgId
- final int clientErrMsgId = errMsgId == FACE_ERROR_VENDOR
- ? (vendorCode + FACE_ERROR_VENDOR_BASE) : errMsgId;
- if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onEnrollmentError(clientErrMsgId,
- getErrorString(mContext, errMsgId, vendorCode));
- } else if (mAuthenticationCallback != null) {
- mAuthenticationCallback.onAuthenticationError(clientErrMsgId,
- getErrorString(mContext, errMsgId, vendorCode));
- } else if (mRemovalCallback != null) {
- mRemovalCallback.onRemovalError(mRemovalFace, clientErrMsgId,
- getErrorString(mContext, errMsgId, vendorCode));
- } else if (mFaceDetectionCallback != null) {
- mFaceDetectionCallback.onDetectionError(errMsgId);
- mFaceDetectionCallback = null;
- }
- }
-
- private void sendEnrollResult(Face face, int remaining) {
- if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onEnrollmentProgress(remaining);
- }
- }
-
- private void sendAuthenticatedSucceeded(Face face, int userId, boolean isStrongBiometric) {
- if (mAuthenticationCallback != null) {
- final AuthenticationResult result =
- new AuthenticationResult(mCryptoObject, face, userId, isStrongBiometric);
- mAuthenticationCallback.onAuthenticationSucceeded(result);
- }
- }
-
- private void sendAuthenticatedFailed() {
- if (mAuthenticationCallback != null) {
- mAuthenticationCallback.onAuthenticationFailed();
- }
- }
-
- private void sendAcquiredResult(int acquireInfo, int vendorCode) {
- if (mAuthenticationCallback != null) {
- final FaceAuthenticationFrame frame = new FaceAuthenticationFrame(
- new FaceDataFrame(acquireInfo, vendorCode));
- sendAuthenticationFrame(frame);
- } else if (mEnrollmentCallback != null) {
- final FaceEnrollFrame frame = new FaceEnrollFrame(
- null /* cell */,
- FaceEnrollStages.UNKNOWN,
- new FaceDataFrame(acquireInfo, vendorCode));
- sendEnrollmentFrame(frame);
- }
- }
-
- private void sendAuthenticationFrame(@Nullable FaceAuthenticationFrame frame) {
- if (frame == null) {
- Slog.w(TAG, "Received null authentication frame");
- } else if (mAuthenticationCallback != null) {
- // TODO(b/178414967): Send additional frame data to callback
- final int acquireInfo = frame.getData().getAcquiredInfo();
- final int vendorCode = frame.getData().getVendorCode();
- final int helpCode = getHelpCode(acquireInfo, vendorCode);
- final String helpMessage = getAuthHelpMessage(mContext, acquireInfo, vendorCode);
- mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
-
- // Ensure that only non-null help messages are sent.
- if (helpMessage != null) {
- mAuthenticationCallback.onAuthenticationHelp(helpCode, helpMessage);
- }
- }
- }
-
- private void sendEnrollmentFrame(@Nullable FaceEnrollFrame frame) {
- if (frame == null) {
- Slog.w(TAG, "Received null enrollment frame");
- } else if (mEnrollmentCallback != null) {
- final FaceDataFrame data = frame.getData();
- final int acquireInfo = data.getAcquiredInfo();
- final int vendorCode = data.getVendorCode();
- final int helpCode = getHelpCode(acquireInfo, vendorCode);
- final String helpMessage = getEnrollHelpMessage(mContext, acquireInfo, vendorCode);
- mEnrollmentCallback.onEnrollmentFrame(
- helpCode,
- helpMessage,
- frame.getCell(),
- frame.getStage(),
- data.getPan(),
- data.getTilt(),
- data.getDistance());
- }
- }
-
- private static int getHelpCode(int acquireInfo, int vendorCode) {
- return acquireInfo == FACE_ACQUIRED_VENDOR
- ? vendorCode + FACE_ACQUIRED_VENDOR_BASE
- : acquireInfo;
- }
-
/**
* @hide
*/
diff --git a/core/java/android/hardware/fingerprint/FingerprintCallback.java b/core/java/android/hardware/fingerprint/FingerprintCallback.java
new file mode 100644
index 000000000000..e4fbe6e09709
--- /dev/null
+++ b/core/java/android/hardware/fingerprint/FingerprintCallback.java
@@ -0,0 +1,299 @@
+/*
+ * 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 android.hardware.fingerprint;
+
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR_BASE;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR_BASE;
+import static android.hardware.fingerprint.FingerprintManager.getAcquiredString;
+import static android.hardware.fingerprint.FingerprintManager.getErrorString;
+
+import android.annotation.IntDef;
+import android.content.Context;
+import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
+import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
+import android.hardware.fingerprint.FingerprintManager.CryptoObject;
+import android.hardware.fingerprint.FingerprintManager.EnrollmentCallback;
+import android.hardware.fingerprint.FingerprintManager.FingerprintDetectionCallback;
+import android.hardware.fingerprint.FingerprintManager.GenerateChallengeCallback;
+import android.hardware.fingerprint.FingerprintManager.RemovalCallback;
+import android.util.Slog;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * Encapsulates callbacks and client specific information for each fingerprint related request.
+ * @hide
+ */
+public class FingerprintCallback {
+ private static final String TAG = "FingerprintCallback";
+ public static final int REMOVE_SINGLE = 1;
+ public static final int REMOVE_ALL = 2;
+ @IntDef({REMOVE_SINGLE, REMOVE_ALL})
+ public @interface RemoveRequest {}
+ @Nullable
+ private AuthenticationCallback mAuthenticationCallback;
+ @Nullable
+ private EnrollmentCallback mEnrollmentCallback;
+ @Nullable
+ private RemovalCallback mRemovalCallback;
+ @Nullable
+ private GenerateChallengeCallback mGenerateChallengeCallback;
+ @Nullable
+ private FingerprintDetectionCallback mFingerprintDetectionCallback;
+ @Nullable
+ private CryptoObject mCryptoObject;
+ @Nullable
+ private @RemoveRequest int mRemoveRequest;
+ @Nullable
+ private Fingerprint mRemoveFingerprint;
+
+ /**
+ * Construction for fingerprint authentication client callback.
+ */
+ FingerprintCallback(@NonNull AuthenticationCallback authenticationCallback,
+ @Nullable CryptoObject cryptoObject) {
+ mAuthenticationCallback = authenticationCallback;
+ mCryptoObject = cryptoObject;
+ }
+
+ /**
+ * Construction for fingerprint detect client callback.
+ */
+ FingerprintCallback(@NonNull FingerprintDetectionCallback fingerprintDetectionCallback) {
+ mFingerprintDetectionCallback = fingerprintDetectionCallback;
+ }
+
+ /**
+ * Construction for fingerprint enroll client callback.
+ */
+ FingerprintCallback(@NonNull EnrollmentCallback enrollmentCallback) {
+ mEnrollmentCallback = enrollmentCallback;
+ }
+
+ /**
+ * Construction for fingerprint generate challenge client callback.
+ */
+ FingerprintCallback(@NonNull GenerateChallengeCallback generateChallengeCallback) {
+ mGenerateChallengeCallback = generateChallengeCallback;
+ }
+
+ /**
+ * Construction for fingerprint removal client callback.
+ */
+ FingerprintCallback(@NonNull RemovalCallback removalCallback, @RemoveRequest int removeRequest,
+ @Nullable Fingerprint removeFingerprint) {
+ mRemovalCallback = removalCallback;
+ mRemoveRequest = removeRequest;
+ mRemoveFingerprint = removeFingerprint;
+ }
+
+ /**
+ * Propagate enroll progress via the callback.
+ * @param remaining number of enrollment steps remaining
+ */
+ public void sendEnrollResult(int remaining) {
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onEnrollmentProgress(remaining);
+ }
+ }
+
+ /**
+ * Propagate remove face completed via the callback.
+ * @param fingerprint removed identifier
+ * @param remaining number of face enrollments remaining
+ */
+ public void sendRemovedResult(@Nullable Fingerprint fingerprint, int remaining) {
+ if (mRemovalCallback == null) {
+ return;
+ }
+
+ if (mRemoveRequest == REMOVE_SINGLE) {
+ if (fingerprint == null) {
+ Slog.e(TAG, "Received MSG_REMOVED, but fingerprint is null");
+ return;
+ }
+
+ if (mRemoveFingerprint == null) {
+ Slog.e(TAG, "Missing fingerprint");
+ return;
+ }
+
+ final int fingerId = fingerprint.getBiometricId();
+ int reqFingerId = mRemoveFingerprint.getBiometricId();
+ if (reqFingerId != 0 && fingerId != 0 && fingerId != reqFingerId) {
+ Slog.w(TAG, "Finger id didn't match: " + fingerId + " != " + reqFingerId);
+ return;
+ }
+ }
+
+ mRemovalCallback.onRemovalSucceeded(fingerprint, remaining);
+ }
+
+ /**
+ * Propagate authentication succeeded via the callback.
+ * @param fingerprint matched identifier
+ * @param userId id of the corresponding user
+ * @param isStrongBiometric if the sensor is strong or not
+ */
+ public void sendAuthenticatedSucceeded(@NonNull Fingerprint fingerprint, int userId,
+ boolean isStrongBiometric) {
+ if (mAuthenticationCallback == null) {
+ Slog.e(TAG, "Authentication succeeded but callback is null.");
+ return;
+ }
+
+ final AuthenticationResult result = new AuthenticationResult(mCryptoObject, fingerprint,
+ userId, isStrongBiometric);
+ mAuthenticationCallback.onAuthenticationSucceeded(result);
+ }
+
+ /**
+ * Propagate authentication failed via the callback.
+ */
+ public void sendAuthenticatedFailed() {
+ if (mAuthenticationCallback != null) {
+ mAuthenticationCallback.onAuthenticationFailed();
+ }
+ }
+
+ /**
+ * Propagate acquired result via the callback.
+ * @param context corresponding context
+ * @param acquireInfo represents the framework acquired id
+ * @param vendorCode represents the vendor acquired code
+ */
+ public void sendAcquiredResult(@NonNull Context context, int acquireInfo, int vendorCode) {
+ if (mAuthenticationCallback != null) {
+ mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
+ }
+ if (mEnrollmentCallback != null && acquireInfo != FINGERPRINT_ACQUIRED_START) {
+ mEnrollmentCallback.onAcquired(acquireInfo == FINGERPRINT_ACQUIRED_GOOD);
+ }
+ final String msg = getAcquiredString(context, acquireInfo, vendorCode);
+ if (msg == null) {
+ return;
+ }
+ // emulate HAL 2.1 behavior and send real acquiredInfo
+ final int clientInfo = acquireInfo == FINGERPRINT_ACQUIRED_VENDOR
+ ? (vendorCode + FINGERPRINT_ACQUIRED_VENDOR_BASE) : acquireInfo;
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onEnrollmentHelp(clientInfo, msg);
+ } else if (mAuthenticationCallback != null) {
+ if (acquireInfo != FINGERPRINT_ACQUIRED_START) {
+ mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg);
+ }
+ }
+ }
+
+ /**
+ * Propagate errors via the callback.
+ * @param context corresponding context
+ * @param errMsgId represents the framework error id
+ * @param vendorCode represents the vendor error code
+ */
+ public void sendErrorResult(@NonNull Context context, int errMsgId, int vendorCode) {
+ // emulate HAL 2.1 behavior and send real errMsgId
+ final int clientErrMsgId = errMsgId == FINGERPRINT_ERROR_VENDOR
+ ? (vendorCode + FINGERPRINT_ERROR_VENDOR_BASE) : errMsgId;
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onEnrollmentError(clientErrMsgId,
+ getErrorString(context, errMsgId, vendorCode));
+ } else if (mAuthenticationCallback != null) {
+ mAuthenticationCallback.onAuthenticationError(clientErrMsgId,
+ getErrorString(context, errMsgId, vendorCode));
+ } else if (mRemovalCallback != null) {
+ mRemovalCallback.onRemovalError(mRemoveFingerprint, clientErrMsgId,
+ getErrorString(context, errMsgId, vendorCode));
+ } else if (mFingerprintDetectionCallback != null) {
+ mFingerprintDetectionCallback.onDetectionError(errMsgId);
+ mFingerprintDetectionCallback = null;
+ }
+ }
+
+ /**
+ * Propagate challenge generated completed via the callback.
+ * @param sensorId id of the corresponding sensor
+ * @param userId id of the corresponding sensor
+ * @param challenge value of the challenge generated
+ */
+ public void sendChallengeGenerated(long challenge, int sensorId, int userId) {
+ if (mGenerateChallengeCallback == null) {
+ Slog.e(TAG, "sendChallengeGenerated, callback null");
+ return;
+ }
+ mGenerateChallengeCallback.onChallengeGenerated(sensorId, userId, challenge);
+ }
+
+ /**
+ * Propagate fingerprint detected completed via the callback.
+ * @param sensorId id of the corresponding sensor
+ * @param userId id of the corresponding user
+ * @param isStrongBiometric if the sensor is strong or not
+ */
+ public void sendFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) {
+ if (mFingerprintDetectionCallback == null) {
+ Slog.e(TAG, "sendFingerprintDetected, callback null");
+ return;
+ }
+ mFingerprintDetectionCallback.onFingerprintDetected(sensorId, userId, isStrongBiometric);
+ }
+
+ /**
+ * Propagate udfps pointer down via the callback.
+ * @param sensorId id of the corresponding sensor
+ */
+ public void sendUdfpsPointerDown(int sensorId) {
+ if (mAuthenticationCallback == null) {
+ Slog.e(TAG, "sendUdfpsPointerDown, callback null");
+ } else {
+ mAuthenticationCallback.onUdfpsPointerDown(sensorId);
+ }
+
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onUdfpsPointerDown(sensorId);
+ }
+ }
+
+ /**
+ * Propagate udfps pointer up via the callback.
+ * @param sensorId id of the corresponding sensor
+ */
+ public void sendUdfpsPointerUp(int sensorId) {
+ if (mAuthenticationCallback == null) {
+ Slog.e(TAG, "sendUdfpsPointerUp, callback null");
+ } else {
+ mAuthenticationCallback.onUdfpsPointerUp(sensorId);
+ }
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onUdfpsPointerUp(sensorId);
+ }
+ }
+
+ /**
+ * Propagate udfps overlay shown via the callback.
+ */
+ public void sendUdfpsOverlayShown() {
+ if (mEnrollmentCallback != null) {
+ mEnrollmentCallback.onUdfpsOverlayShown();
+ }
+ }
+}
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 81e321d96aa6..37f2fb2a538a 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -25,6 +25,8 @@ import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.Manifest.permission.USE_FINGERPRINT;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
import static android.hardware.biometrics.Flags.FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT;
+import static android.hardware.fingerprint.FingerprintCallback.REMOVE_ALL;
+import static android.hardware.fingerprint.FingerprintCallback.REMOVE_SINGLE;
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
import static com.android.internal.util.FrameworkStatsLog.AUTH_DEPRECATED_APIUSED__DEPRECATED_API__API_FINGERPRINT_MANAGER_AUTHENTICATE;
@@ -57,9 +59,9 @@ import android.os.Build;
import android.os.CancellationSignal;
import android.os.CancellationSignal.OnCancelListener;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.IRemoteCallback;
-import android.os.Looper;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -94,19 +96,6 @@ import javax.crypto.Mac;
@RequiresFeature(PackageManager.FEATURE_FINGERPRINT)
public class FingerprintManager implements BiometricAuthenticator, BiometricFingerprintConstants {
private static final String TAG = "FingerprintManager";
- private static final boolean DEBUG = true;
- private static final int MSG_ENROLL_RESULT = 100;
- private static final int MSG_ACQUIRED = 101;
- private static final int MSG_AUTHENTICATION_SUCCEEDED = 102;
- private static final int MSG_AUTHENTICATION_FAILED = 103;
- private static final int MSG_ERROR = 104;
- private static final int MSG_REMOVED = 105;
- private static final int MSG_CHALLENGE_GENERATED = 106;
- private static final int MSG_FINGERPRINT_DETECTED = 107;
- private static final int MSG_UDFPS_POINTER_DOWN = 108;
- private static final int MSG_UDFPS_POINTER_UP = 109;
- private static final int MSG_POWER_BUTTON_PRESSED = 110;
- private static final int MSG_UDFPS_OVERLAY_SHOWN = 111;
/**
* @hide
@@ -148,34 +137,14 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
*/
public static final int SENSOR_ID_ANY = -1;
- private static class RemoveTracker {
- static final int REMOVE_SINGLE = 1;
- static final int REMOVE_ALL = 2;
- @IntDef({REMOVE_SINGLE, REMOVE_ALL})
- @interface RemoveRequest {}
+ private final IFingerprintService mService;
+ private final Context mContext;
+ private final IBinder mToken = new Binder();
- final @RemoveRequest int mRemoveRequest;
- @Nullable final Fingerprint mSingleFingerprint;
-
- RemoveTracker(@RemoveRequest int request, @Nullable Fingerprint fingerprint) {
- mRemoveRequest = request;
- mSingleFingerprint = fingerprint;
- }
- }
-
- private IFingerprintService mService;
- private Context mContext;
- private IBinder mToken = new Binder();
- private AuthenticationCallback mAuthenticationCallback;
- private FingerprintDetectionCallback mFingerprintDetectionCallback;
- private EnrollmentCallback mEnrollmentCallback;
- private RemovalCallback mRemovalCallback;
- private GenerateChallengeCallback mGenerateChallengeCallback;
- private CryptoObject mCryptoObject;
- @Nullable private RemoveTracker mRemoveTracker;
private Handler mHandler;
@Nullable private float[] mEnrollStageThresholds;
private List<FingerprintSensorPropertiesInternal> mProps = new ArrayList<>();
+ private HandlerExecutor mExecutor;
/**
* Retrieves a list of properties for all fingerprint sensors on the device.
@@ -395,7 +364,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
* @deprecated See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationCallback}
*/
@Deprecated
- public static abstract class AuthenticationCallback
+ public abstract static class AuthenticationCallback
extends BiometricAuthenticator.AuthenticationCallback {
/**
* Called when an unrecoverable error has been encountered and the operation is complete.
@@ -479,7 +448,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
*
* @hide
*/
- public static abstract class EnrollmentCallback {
+ public abstract static class EnrollmentCallback {
/**
* Called when an unrecoverable error has been encountered and the operation is complete.
* No further callbacks will be made on this object.
@@ -536,7 +505,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
*
* @hide
*/
- public static abstract class RemovalCallback {
+ public abstract static class RemovalCallback {
/**
* Called when the given fingerprint can't be removed.
* @param fp The fingerprint that the call attempted to remove
@@ -559,7 +528,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
/**
* @hide
*/
- public static abstract class LockoutResetCallback {
+ public abstract static class LockoutResetCallback {
/**
* Called when lockout period expired and clients are allowed to listen for fingerprint
@@ -584,9 +553,11 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
*/
private void useHandler(Handler handler) {
if (handler != null) {
- mHandler = new MyHandler(handler.getLooper());
- } else if (mHandler.getLooper() != mContext.getMainLooper()) {
- mHandler = new MyHandler(mContext.getMainLooper());
+ mHandler = handler;
+ mExecutor = new HandlerExecutor(mHandler);
+ } else if (mHandler != mContext.getMainThreadHandler()) {
+ mHandler = mContext.getMainThreadHandler();
+ mExecutor = new HandlerExecutor(mHandler);
}
}
@@ -676,11 +647,12 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
if (mService != null) {
try {
+ final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback,
+ crypto);
useHandler(handler);
- mAuthenticationCallback = callback;
- mCryptoObject = crypto;
final long operationId = crypto != null ? crypto.getOpId() : 0;
- final long authId = mService.authenticate(mToken, operationId, mServiceReceiver, options);
+ final long authId = mService.authenticate(mToken, operationId,
+ new FingerprintServiceReceiver(fingerprintCallback), options);
if (cancel != null) {
cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
}
@@ -715,10 +687,11 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
options.setOpPackageName(mContext.getOpPackageName());
options.setAttributionTag(mContext.getAttributionTag());
- mFingerprintDetectionCallback = callback;
+ final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback);
try {
- final long authId = mService.detectFingerprint(mToken, mServiceReceiver, options);
+ final long authId = mService.detectFingerprint(mToken,
+ new FingerprintServiceReceiver(fingerprintCallback), options);
cancel.setOnCancelListener(new OnFingerprintDetectionCancelListener(authId));
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception when requesting finger detect", e);
@@ -767,9 +740,10 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
if (mService != null) {
try {
- mEnrollmentCallback = callback;
+ final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback);
final long enrollId = mService.enroll(mToken, hardwareAuthToken, userId,
- mServiceReceiver, mContext.getOpPackageName(), enrollReason, options);
+ new FingerprintServiceReceiver(fingerprintCallback),
+ mContext.getOpPackageName(), enrollReason, options);
if (cancel != null) {
cancel.setOnCancelListener(new OnEnrollCancelListener(enrollId));
}
@@ -799,12 +773,13 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
@RequiresPermission(MANAGE_FINGERPRINT)
public void generateChallenge(int sensorId, int userId, GenerateChallengeCallback callback) {
if (mService != null) try {
- mGenerateChallengeCallback = callback;
- mService.generateChallenge(mToken, sensorId, userId, mServiceReceiver,
- mContext.getOpPackageName());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback);
+ mService.generateChallenge(mToken, sensorId, userId,
+ new FingerprintServiceReceiver(fingerprintCallback),
+ mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -875,13 +850,14 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
@RequiresPermission(MANAGE_FINGERPRINT)
public void remove(Fingerprint fp, int userId, RemovalCallback callback) {
if (mService != null) try {
- mRemovalCallback = callback;
- mRemoveTracker = new RemoveTracker(RemoveTracker.REMOVE_SINGLE, fp);
- mService.remove(mToken, fp.getBiometricId(), userId, mServiceReceiver,
- mContext.getOpPackageName());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback,
+ REMOVE_SINGLE, fp);
+ mService.remove(mToken, fp.getBiometricId(), userId,
+ new FingerprintServiceReceiver(fingerprintCallback),
+ mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -892,9 +868,11 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
public void removeAll(int userId, @NonNull RemovalCallback callback) {
if (mService != null) {
try {
- mRemovalCallback = callback;
- mRemoveTracker = new RemoveTracker(RemoveTracker.REMOVE_ALL, null /* fp */);
- mService.removeAll(mToken, userId, mServiceReceiver, mContext.getOpPackageName());
+ final FingerprintCallback fingerprintCallback = new FingerprintCallback(callback,
+ REMOVE_ALL, null);
+ mService.removeAll(mToken, userId,
+ new FingerprintServiceReceiver(fingerprintCallback),
+ mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1162,7 +1140,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
public void onPowerPressed() {
Slog.i(TAG, "onPowerPressed");
- mHandler.obtainMessage(MSG_POWER_BUTTON_PRESSED).sendToTarget();
+ mExecutor.execute(() -> sendPowerPressed());
}
/**
@@ -1346,199 +1324,6 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
}
}
- private class MyHandler extends Handler {
- private MyHandler(Context context) {
- super(context.getMainLooper());
- }
-
- private MyHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(android.os.Message msg) {
- switch (msg.what) {
- case MSG_ENROLL_RESULT:
- sendEnrollResult((Fingerprint) msg.obj, msg.arg1 /* remaining */);
- break;
- case MSG_ACQUIRED:
- sendAcquiredResult(msg.arg1 /* acquire info */,
- msg.arg2 /* vendorCode */);
- break;
- case MSG_AUTHENTICATION_SUCCEEDED:
- sendAuthenticatedSucceeded((Fingerprint) msg.obj, msg.arg1 /* userId */,
- msg.arg2 == 1 /* isStrongBiometric */);
- break;
- case MSG_AUTHENTICATION_FAILED:
- sendAuthenticatedFailed();
- break;
- case MSG_ERROR:
- sendErrorResult(msg.arg1 /* errMsgId */, msg.arg2 /* vendorCode */);
- break;
- case MSG_REMOVED:
- sendRemovedResult((Fingerprint) msg.obj, msg.arg1 /* remaining */);
- break;
- case MSG_CHALLENGE_GENERATED:
- sendChallengeGenerated(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
- (long) msg.obj /* challenge */);
- break;
- case MSG_FINGERPRINT_DETECTED:
- sendFingerprintDetected(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
- (boolean) msg.obj /* isStrongBiometric */);
- break;
- case MSG_UDFPS_POINTER_DOWN:
- sendUdfpsPointerDown(msg.arg1 /* sensorId */);
- break;
- case MSG_UDFPS_POINTER_UP:
- sendUdfpsPointerUp(msg.arg1 /* sensorId */);
- break;
- case MSG_POWER_BUTTON_PRESSED:
- sendPowerPressed();
- break;
- case MSG_UDFPS_OVERLAY_SHOWN:
- sendUdfpsOverlayShown();
- default:
- Slog.w(TAG, "Unknown message: " + msg.what);
-
- }
- }
- }
-
- private void sendRemovedResult(Fingerprint fingerprint, int remaining) {
- if (mRemovalCallback == null) {
- return;
- }
-
- if (mRemoveTracker == null) {
- Slog.w(TAG, "Removal tracker is null");
- return;
- }
-
- if (mRemoveTracker.mRemoveRequest == RemoveTracker.REMOVE_SINGLE) {
- if (fingerprint == null) {
- Slog.e(TAG, "Received MSG_REMOVED, but fingerprint is null");
- return;
- }
-
- if (mRemoveTracker.mSingleFingerprint == null) {
- Slog.e(TAG, "Missing fingerprint");
- return;
- }
-
- final int fingerId = fingerprint.getBiometricId();
- int reqFingerId = mRemoveTracker.mSingleFingerprint.getBiometricId();
- if (reqFingerId != 0 && fingerId != 0 && fingerId != reqFingerId) {
- Slog.w(TAG, "Finger id didn't match: " + fingerId + " != " + reqFingerId);
- return;
- }
- }
-
- mRemovalCallback.onRemovalSucceeded(fingerprint, remaining);
- }
-
- private void sendEnrollResult(Fingerprint fp, int remaining) {
- if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onEnrollmentProgress(remaining);
- }
- }
-
- private void sendAuthenticatedSucceeded(Fingerprint fp, int userId, boolean isStrongBiometric) {
- if (mAuthenticationCallback != null) {
- final AuthenticationResult result =
- new AuthenticationResult(mCryptoObject, fp, userId, isStrongBiometric);
- mAuthenticationCallback.onAuthenticationSucceeded(result);
- }
- }
-
- private void sendAuthenticatedFailed() {
- if (mAuthenticationCallback != null) {
- mAuthenticationCallback.onAuthenticationFailed();
- }
- }
-
- private void sendAcquiredResult(int acquireInfo, int vendorCode) {
- if (mAuthenticationCallback != null) {
- mAuthenticationCallback.onAuthenticationAcquired(acquireInfo);
- }
- if (mEnrollmentCallback != null && acquireInfo != FINGERPRINT_ACQUIRED_START) {
- mEnrollmentCallback.onAcquired(acquireInfo == FINGERPRINT_ACQUIRED_GOOD);
- }
- final String msg = getAcquiredString(mContext, acquireInfo, vendorCode);
- if (msg == null) {
- return;
- }
- // emulate HAL 2.1 behavior and send real acquiredInfo
- final int clientInfo = acquireInfo == FINGERPRINT_ACQUIRED_VENDOR
- ? (vendorCode + FINGERPRINT_ACQUIRED_VENDOR_BASE) : acquireInfo;
- if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onEnrollmentHelp(clientInfo, msg);
- } else if (mAuthenticationCallback != null) {
- if (acquireInfo != BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START) {
- mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg);
- }
- }
- }
-
- private void sendErrorResult(int errMsgId, int vendorCode) {
- // emulate HAL 2.1 behavior and send real errMsgId
- final int clientErrMsgId = errMsgId == FINGERPRINT_ERROR_VENDOR
- ? (vendorCode + FINGERPRINT_ERROR_VENDOR_BASE) : errMsgId;
- if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onEnrollmentError(clientErrMsgId,
- getErrorString(mContext, errMsgId, vendorCode));
- } else if (mAuthenticationCallback != null) {
- mAuthenticationCallback.onAuthenticationError(clientErrMsgId,
- getErrorString(mContext, errMsgId, vendorCode));
- } else if (mRemovalCallback != null) {
- final Fingerprint fp = mRemoveTracker != null
- ? mRemoveTracker.mSingleFingerprint : null;
- mRemovalCallback.onRemovalError(fp, clientErrMsgId,
- getErrorString(mContext, errMsgId, vendorCode));
- } else if (mFingerprintDetectionCallback != null) {
- mFingerprintDetectionCallback.onDetectionError(errMsgId);
- mFingerprintDetectionCallback = null;
- }
- }
-
- private void sendChallengeGenerated(int sensorId, int userId, long challenge) {
- if (mGenerateChallengeCallback == null) {
- Slog.e(TAG, "sendChallengeGenerated, callback null");
- return;
- }
- mGenerateChallengeCallback.onChallengeGenerated(sensorId, userId, challenge);
- }
-
- private void sendFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) {
- if (mFingerprintDetectionCallback == null) {
- Slog.e(TAG, "sendFingerprintDetected, callback null");
- return;
- }
- mFingerprintDetectionCallback.onFingerprintDetected(sensorId, userId, isStrongBiometric);
- }
-
- private void sendUdfpsPointerDown(int sensorId) {
- if (mAuthenticationCallback == null) {
- Slog.e(TAG, "sendUdfpsPointerDown, callback null");
- } else {
- mAuthenticationCallback.onUdfpsPointerDown(sensorId);
- }
-
- if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onUdfpsPointerDown(sensorId);
- }
- }
-
- private void sendUdfpsPointerUp(int sensorId) {
- if (mAuthenticationCallback == null) {
- Slog.e(TAG, "sendUdfpsPointerUp, callback null");
- } else {
- mAuthenticationCallback.onUdfpsPointerUp(sensorId);
- }
- if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onUdfpsPointerUp(sensorId);
- }
- }
-
private void sendPowerPressed() {
try {
mService.onPowerPressed();
@@ -1547,12 +1332,6 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
}
}
- private void sendUdfpsOverlayShown() {
- if (mEnrollmentCallback != null) {
- mEnrollmentCallback.onUdfpsOverlayShown();
- }
- }
-
/**
* @hide
*/
@@ -1562,7 +1341,6 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
if (mService == null) {
Slog.v(TAG, "FingerprintService was null");
}
- mHandler = new MyHandler(context);
if (context.checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL)
== PackageManager.PERMISSION_GRANTED) {
addAuthenticatorsRegisteredCallback(
@@ -1574,6 +1352,8 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
}
});
}
+ mHandler = context.getMainThreadHandler();
+ mExecutor = new HandlerExecutor(mHandler);
}
private int getCurrentUserId() {
@@ -1773,66 +1553,72 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
return null;
}
- private IFingerprintServiceReceiver mServiceReceiver = new IFingerprintServiceReceiver.Stub() {
+ class FingerprintServiceReceiver extends IFingerprintServiceReceiver.Stub {
+ private final FingerprintCallback mFingerprintCallback;
+
+ FingerprintServiceReceiver(FingerprintCallback fingerprintCallback) {
+ mFingerprintCallback = fingerprintCallback;
+ }
@Override // binder call
public void onEnrollResult(Fingerprint fp, int remaining) {
- mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0, fp).sendToTarget();
+ mExecutor.execute(() -> mFingerprintCallback.sendEnrollResult(remaining));
}
@Override // binder call
public void onAcquired(int acquireInfo, int vendorCode) {
- mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode).sendToTarget();
+ mExecutor.execute(() -> mFingerprintCallback.sendAcquiredResult(mContext, acquireInfo,
+ vendorCode));
}
@Override // binder call
public void onAuthenticationSucceeded(Fingerprint fp, int userId,
boolean isStrongBiometric) {
- mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, isStrongBiometric ? 1 : 0,
- fp).sendToTarget();
+ mExecutor.execute(() -> mFingerprintCallback.sendAuthenticatedSucceeded(fp, userId,
+ isStrongBiometric));
}
@Override
public void onFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) {
- mHandler.obtainMessage(MSG_FINGERPRINT_DETECTED, sensorId, userId, isStrongBiometric)
- .sendToTarget();
+ mExecutor.execute(() -> mFingerprintCallback.sendFingerprintDetected(sensorId, userId,
+ isStrongBiometric));
}
@Override // binder call
public void onAuthenticationFailed() {
- mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();
+ mExecutor.execute(mFingerprintCallback::sendAuthenticatedFailed);
}
@Override // binder call
public void onError(int error, int vendorCode) {
- mHandler.obtainMessage(MSG_ERROR, error, vendorCode).sendToTarget();
+ mExecutor.execute(() -> mFingerprintCallback.sendErrorResult(mContext, error,
+ vendorCode));
}
@Override // binder call
public void onRemoved(Fingerprint fp, int remaining) {
- mHandler.obtainMessage(MSG_REMOVED, remaining, 0, fp).sendToTarget();
+ mExecutor.execute(() -> mFingerprintCallback.sendRemovedResult(fp, remaining));
}
@Override // binder call
public void onChallengeGenerated(int sensorId, int userId, long challenge) {
- mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, sensorId, userId, challenge)
- .sendToTarget();
+ mExecutor.execute(() -> mFingerprintCallback.sendChallengeGenerated(challenge, sensorId,
+ userId));
}
@Override // binder call
public void onUdfpsPointerDown(int sensorId) {
- mHandler.obtainMessage(MSG_UDFPS_POINTER_DOWN, sensorId, 0).sendToTarget();
+ mExecutor.execute(() -> mFingerprintCallback.sendUdfpsPointerDown(sensorId));
}
@Override // binder call
public void onUdfpsPointerUp(int sensorId) {
- mHandler.obtainMessage(MSG_UDFPS_POINTER_UP, sensorId, 0).sendToTarget();
+ mExecutor.execute(() -> mFingerprintCallback.sendUdfpsPointerUp(sensorId));
}
@Override
public void onUdfpsOverlayShown() {
- mHandler.obtainMessage(MSG_UDFPS_OVERLAY_SHOWN).sendToTarget();
+ mExecutor.execute(mFingerprintCallback::sendUdfpsOverlayShown);
}
- };
-
+ }
}
diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl
index 4ae0a5733f08..3b5990181d8b 100644
--- a/core/java/android/permission/IPermissionManager.aidl
+++ b/core/java/android/permission/IPermissionManager.aidl
@@ -98,6 +98,8 @@ interface IPermissionManager {
IBinder registerAttributionSource(in AttributionSourceState source);
+ int getNumRegisteredAttributionSources(int uid);
+
boolean isRegisteredAttributionSource(in AttributionSourceState source);
int checkPermission(String packageName, String permissionName, String persistentDeviceId,
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index fe3fa8cf34f5..2daf4ac8226e 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -1675,6 +1675,21 @@ public final class PermissionManager {
}
/**
+ * Gets the number of currently registered attribution sources for a particular UID. This should
+ * only be used for testing purposes.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.UPDATE_APP_OPS_STATS)
+ public int getNumRegisteredAttributionSourcesForTest(int uid) {
+ try {
+ return mPermissionManager.getNumRegisteredAttributionSources(uid);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return -1;
+ }
+
+ /**
* Revoke the POST_NOTIFICATIONS permission, without killing the app. This method must ONLY BE
* USED in CTS or local tests.
*
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 23ece310b926..abb4917024ce 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -125,6 +125,30 @@ flag {
}
flag {
+ name: "sensitive_content_metrics_bugfix"
+ namespace: "permissions"
+ description: "Enables metrics bugfixes for sensitive content/notification features"
+ bug: "312784351"
+ # Referenced in WM where WM starts before DeviceConfig
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "sensitive_content_recents_screenshot_bugfix"
+ namespace: "permissions"
+ description: "Enables recents screenshot bugfixes for sensitive content/notification features"
+ bug: "312784351"
+ # Referenced in WM where WM starts before DeviceConfig
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "device_aware_permissions_enabled"
is_fixed_read_only: true
namespace: "permissions"
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index dd93972820c0..6ad7422cfff0 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9020,6 +9020,16 @@ public final class Settings {
"accessibility_display_daltonizer";
/**
+ * Integer property that determines the saturation level of color correction. Default value
+ * is defined in Settings config.xml.
+ * [0-10] inclusive where 0 would look as if color space adustment is not applied at all.
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL =
+ "accessibility_display_daltonizer_saturation_level";
+
+ /**
* Setting that specifies whether automatic click when the mouse pointer stops moving is
* enabled.
*
diff --git a/core/java/android/security/IFileIntegrityService.aidl b/core/java/android/security/IFileIntegrityService.aidl
index 1a6cf881e337..ddb662ad42cb 100644
--- a/core/java/android/security/IFileIntegrityService.aidl
+++ b/core/java/android/security/IFileIntegrityService.aidl
@@ -28,6 +28,8 @@ interface IFileIntegrityService {
boolean isAppSourceCertificateTrusted(in byte[] certificateBytes, in String packageName);
IInstalld.IFsveritySetupAuthToken createAuthToken(in ParcelFileDescriptor authFd);
+
+ @EnforcePermission("SETUP_FSVERITY")
int setupFsverity(IInstalld.IFsveritySetupAuthToken authToken, in String filePath,
in String packageName);
}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 4d4cb6c09c15..815fc5820189 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -87,12 +87,6 @@ public class FeatureFlagUtils {
"settings_need_connected_ble_device_for_broadcast";
/**
- * Enable new language and keyboard settings UI
- * @hide
- */
- public static final String SETTINGS_NEW_KEYBOARD_UI = "settings_new_keyboard_ui";
-
- /**
* Enable new modifier key settings UI
* @hide
*/
@@ -221,7 +215,6 @@ public class FeatureFlagUtils {
DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true");
DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true");
DEFAULT_FLAGS.put(SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, "true");
- DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "true");
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "true");
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "true");
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE, "false");
@@ -249,7 +242,6 @@ public class FeatureFlagUtils {
PERSISTENT_FLAGS.add(SETTINGS_SUPPORT_LARGE_SCREEN);
PERSISTENT_FLAGS.add(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS);
PERSISTENT_FLAGS.add(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME);
- PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_UI);
PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY);
PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_TRACKPAD);
PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE);
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index 3e615394f7de..f315f55c7ed4 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -48,6 +48,7 @@ import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.accessibility.AccessibilityNodeProvider;
import android.view.accessibility.AccessibilityRequestPreparer;
+import android.view.accessibility.Flags;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
import android.window.ScreenCapture;
@@ -1286,6 +1287,15 @@ public final class AccessibilityInteractionController {
}
/**
+ * Destroy {@link AccessibilityInteractionController} and clean up the pending actions.
+ */
+ public void destroy() {
+ if (Flags.preventLeakingViewrootimpl()) {
+ mHandler.removeCallbacksAndMessages(null);
+ }
+ }
+
+ /**
* This class encapsulates a prefetching strategy for the accessibility APIs for
* querying window content. It is responsible to prefetch a batch of
* AccessibilityNodeInfos in addition to the one for a requested node.
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index ac1f646d9f3f..4c4a22cc96e9 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -13443,6 +13443,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * @return True if the window has the {@link OnContentApplyWindowInsetsListener}, and this means
+ * the framework will apply window insets on the content of the window.
+ * @hide
+ */
+ protected boolean hasContentOnApplyWindowInsetsListener() {
+ return mAttachInfo != null && mAttachInfo.mContentOnApplyWindowInsetsListener != null;
+ }
+
+ /**
* Sets whether or not this view should account for system screen decorations
* such as the status bar and inset its content; that is, controlling whether
* the default implementation of {@link #fitSystemWindows(Rect)} will be
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 09f98d7e1d82..8d55777a2b8e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -89,6 +89,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FIT_INSETS_CONTROLLED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
@@ -110,12 +111,12 @@ import static android.view.accessibility.Flags.fixMergedContentChangeEventV2;
import static android.view.accessibility.Flags.forceInvertColor;
import static android.view.accessibility.Flags.reduceWindowContentChangedEventThrottle;
import static android.view.flags.Flags.sensitiveContentAppProtection;
+import static android.view.flags.Flags.toolkitFrameRateFunctionEnablingReadOnly;
import static android.view.flags.Flags.toolkitFrameRateTypingReadOnly;
import static android.view.flags.Flags.toolkitFrameRateVelocityMappingReadOnly;
+import static android.view.flags.Flags.toolkitFrameRateViewEnablingReadOnly;
import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision;
import static android.view.flags.Flags.toolkitSetFrameRateReadOnly;
-import static android.view.flags.Flags.toolkitFrameRateFunctionEnablingReadOnly;
-import static android.view.flags.Flags.toolkitFrameRateViewEnablingReadOnly;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
@@ -1441,6 +1442,7 @@ public final class ViewRootImpl implements ViewParent,
// Keep track of the actual window flags supplied by the client.
mClientWindowLayoutFlags = attrs.flags;
+ adjustLayoutInDisplayCutoutMode(attrs);
setAccessibilityFocus(null, null);
if (view instanceof RootViewSurfaceTaker) {
@@ -2043,6 +2045,9 @@ public final class ViewRootImpl implements ViewParent,
final int appearance = mWindowAttributes.insetsFlags.appearance;
final int behavior = mWindowAttributes.insetsFlags.behavior;
+ // Calling this before copying prevents redundant LAYOUT_CHANGED.
+ final int layoutInDisplayCutoutModeFromCaller = adjustLayoutInDisplayCutoutMode(attrs);
+
final int changes = mWindowAttributes.copyFrom(attrs);
if ((changes & WindowManager.LayoutParams.TRANSLUCENT_FLAGS_CHANGED) != 0) {
// Recompute system ui visibility.
@@ -2059,6 +2064,9 @@ public final class ViewRootImpl implements ViewParent,
mWindowAttributes.packageName = mBasePackageName;
}
+ // Restore the layoutInDisplayCutoutMode of the caller;
+ attrs.layoutInDisplayCutoutMode = layoutInDisplayCutoutModeFromCaller;
+
// Restore preserved flags.
mWindowAttributes.systemUiVisibility = systemUiVisibility;
mWindowAttributes.subtreeSystemUiVisibility = subtreeSystemUiVisibility;
@@ -2101,6 +2109,19 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ private int adjustLayoutInDisplayCutoutMode(WindowManager.LayoutParams attrs) {
+ final int originalMode = attrs.layoutInDisplayCutoutMode;
+ if ((attrs.privateFlags & PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED) != 0
+ && attrs.isFullscreen()
+ && attrs.getFitInsetsTypes() == 0
+ && attrs.getFitInsetsSides() == 0) {
+ if (originalMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
+ attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ }
+ }
+ return originalMode;
+ }
+
void handleAppVisibility(boolean visible) {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.instant(Trace.TRACE_TAG_VIEW, TextUtils.formatSimple(
@@ -6088,6 +6109,11 @@ public final class ViewRootImpl implements ViewParent,
mAccessibilityInteractionConnectionManager.ensureNoConnection();
mAccessibilityInteractionConnectionManager.ensureNoDirectConnection();
removeSendWindowContentChangedCallback();
+ if (android.view.accessibility.Flags.preventLeakingViewrootimpl()
+ && mAccessibilityInteractionController != null) {
+ mAccessibilityInteractionController.destroy();
+ mAccessibilityInteractionController = null;
+ }
destroyHardwareRenderer();
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 12ce0f47c460..bdfc236d82e0 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -27,6 +27,7 @@ import android.accessibilityservice.IAccessibilityServiceConnection;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresNoPermission;
import android.annotation.SuppressLint;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -1190,6 +1191,8 @@ public final class AccessibilityInteractionClient
/**
* {@inheritDoc}
*/
+ @Override
+ @RequiresNoPermission
public void setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info,
int interactionId) {
synchronized (mInstanceLock) {
@@ -1231,6 +1234,8 @@ public final class AccessibilityInteractionClient
/**
* {@inheritDoc}
*/
+ @Override
+ @RequiresNoPermission
public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos,
int interactionId) {
synchronized (mInstanceLock) {
@@ -1260,6 +1265,7 @@ public final class AccessibilityInteractionClient
* {@inheritDoc}
*/
@Override
+ @RequiresNoPermission
public void setPrefetchAccessibilityNodeInfoResult(@NonNull List<AccessibilityNodeInfo> infos,
int interactionId) {
int interactionIdWaitingForPrefetchResultCopy = -1;
@@ -1324,6 +1330,8 @@ public final class AccessibilityInteractionClient
/**
* {@inheritDoc}
*/
+ @Override
+ @RequiresNoPermission
public void setPerformAccessibilityActionResult(boolean succeeded, int interactionId) {
synchronized (mInstanceLock) {
if (interactionId > mInteractionId) {
@@ -1372,6 +1380,7 @@ public final class AccessibilityInteractionClient
* @param interactionId The interaction id of the request.
*/
@Override
+ @RequiresNoPermission
public void sendTakeScreenshotOfWindowError(
@AccessibilityService.ScreenshotErrorCode int errorCode, int interactionId) {
synchronized (mInstanceLock) {
@@ -1729,6 +1738,7 @@ public final class AccessibilityInteractionClient
* @param interactionId The interaction id of the request.
*/
@Override
+ @RequiresNoPermission
public void sendAttachOverlayResult(
@AccessibilityService.AttachOverlayResult int result, int interactionId) {
if (!Flags.a11yOverlayCallbacks()) {
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
index fe595193047c..a9e5db5d1873 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl
@@ -34,6 +34,7 @@ oneway interface IAccessibilityInteractionConnectionCallback {
* @param interactionId The interaction id to match the result with the request.
*/
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
+ @RequiresNoPermission
void setFindAccessibilityNodeInfoResult(in AccessibilityNodeInfo info, int interactionId);
/**
@@ -43,6 +44,7 @@ oneway interface IAccessibilityInteractionConnectionCallback {
* @param interactionId The interaction id to match the result with the request.
*/
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
+ @RequiresNoPermission
void setFindAccessibilityNodeInfosResult(in List<AccessibilityNodeInfo> infos,
int interactionId);
@@ -52,6 +54,7 @@ oneway interface IAccessibilityInteractionConnectionCallback {
* @param root The {@link AccessibilityNodeInfo} for which the prefetching is based off of.
* @param infos The result {@link AccessibilityNodeInfo}s.
*/
+ @RequiresNoPermission
void setPrefetchAccessibilityNodeInfoResult(
in List<AccessibilityNodeInfo> infos, int interactionId);
@@ -62,15 +65,18 @@ oneway interface IAccessibilityInteractionConnectionCallback {
* @param interactionId The interaction id to match the result with the request.
*/
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
+ @RequiresNoPermission
void setPerformAccessibilityActionResult(boolean succeeded, int interactionId);
/**
* Sends an error code for a window screenshot request to the requesting client.
*/
+ @RequiresNoPermission
void sendTakeScreenshotOfWindowError(int errorCode, int interactionId);
/**
* Sends an result code for an attach overlay request to the requesting client.
*/
+ @RequiresNoPermission
void sendAttachOverlayResult(int result, int interactionId);
}
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index 334965abd8c9..c9d99d1a407e 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -142,6 +142,16 @@ flag {
}
flag {
+ name: "prevent_leaking_viewrootimpl"
+ namespace: "accessibility"
+ description: "Clear pending messages and callbacks of the handler in AccessibilityInteractionController when the ViewRootImpl is detached from Window to prevent leaking ViewRootImpl"
+ bug: "320701910"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "support_system_pinch_zoom_opt_out_apis"
namespace: "accessibility"
description: "Feature flag for declaring system pinch zoom opt-out apis"
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index a868d487b82f..1ffb4ffd12c2 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -144,17 +144,26 @@ public class WindowTokenClient extends Binder {
if (context == null) {
return;
}
- final ClientTransactionListenerController controller =
- ClientTransactionListenerController.getInstance();
- controller.onContextConfigurationPreChanged(context);
- try {
+ if (shouldReportConfigChange) {
+ // Only report to ClientTransactionListenerController when shouldReportConfigChange,
+ // which is on the MainThread.
+ final ClientTransactionListenerController controller =
+ getClientTransactionListenerController();
+ controller.onContextConfigurationPreChanged(context);
+ try {
+ onConfigurationChangedInner(context, newConfig, newDisplayId,
+ shouldReportConfigChange);
+ } finally {
+ controller.onContextConfigurationPostChanged(context);
+ }
+ } else {
onConfigurationChangedInner(context, newConfig, newDisplayId, shouldReportConfigChange);
- } finally {
- controller.onContextConfigurationPostChanged(context);
}
}
- private void onConfigurationChangedInner(@NonNull Context context,
+ /** Handles onConfiguration changed. */
+ @VisibleForTesting
+ public void onConfigurationChangedInner(@NonNull Context context,
@NonNull Configuration newConfig, int newDisplayId, boolean shouldReportConfigChange) {
CompatibilityInfo.applyOverrideScaleIfNeeded(newConfig);
final boolean displayChanged;
@@ -233,4 +242,11 @@ public class WindowTokenClient extends Binder {
mContextRef.clear();
}
}
+
+ /** Gets {@link ClientTransactionListenerController}. */
+ @VisibleForTesting
+ @NonNull
+ public ClientTransactionListenerController getClientTransactionListenerController() {
+ return ClientTransactionListenerController.getInstance();
+ }
}
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 18413530d575..52487fb1ecaa 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -2486,9 +2486,6 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
params.setFitInsetsSides(0);
params.setFitInsetsTypes(0);
- if (mEdgeToEdgeEnforced) {
- params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- }
}
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java
index bdb33c4b151c..cb1abf13c109 100644
--- a/core/java/com/android/internal/view/menu/ListMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java
@@ -16,10 +16,12 @@
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;
@@ -59,6 +61,8 @@ public class ListMenuItemView extends LinearLayout
private int mMenuType;
+ private boolean mUseNewContextMenu;
+
private LayoutInflater mInflater;
private boolean mForceShowIcon;
@@ -85,6 +89,10 @@ 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) {
@@ -281,7 +289,9 @@ public class ListMenuItemView extends LinearLayout
private void insertIconView() {
LayoutInflater inflater = getInflater();
- mIconView = (ImageView) inflater.inflate(com.android.internal.R.layout.list_menu_item_icon,
+ 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,
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 36828f2dadca..1979e4fe7a90 100644
--- a/core/java/com/android/internal/view/menu/StandardMenuPopup.java
+++ b/core/java/com/android/internal/view/menu/StandardMenuPopup.java
@@ -16,26 +16,24 @@
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;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
-import android.widget.AdapterView.OnItemClickListener;
+import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.ListView;
import android.widget.MenuPopupWindow;
import android.widget.PopupWindow;
-import android.widget.PopupWindow.OnDismissListener;
import android.widget.TextView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.PopupWindow.OnDismissListener;
import java.util.Objects;
@@ -46,8 +44,6 @@ 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;
@@ -57,7 +53,6 @@ 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;
@@ -119,15 +114,10 @@ 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,
- useNewContextMenu ? ITEM_LAYOUT_MATERIAL : ITEM_LAYOUT);
+ mAdapter = new MenuAdapter(menu, inflater, mOverflowOnly, ITEM_LAYOUT);
mPopupStyleAttr = popupStyleAttr;
mPopupStyleRes = popupStyleRes;
diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
index adbf645de74e..0992db91356d 100644
--- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
+++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
@@ -436,7 +436,7 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar
// overlay.
mContentInsets.set(mBaseContentInsets);
mInnerInsets = mBaseInnerInsets;
- if (!mOverlayMode && !stable) {
+ if (!mOverlayMode && !stable && hasContentOnApplyWindowInsetsListener()) {
mContentInsets.top += topInset;
mContentInsets.bottom += bottomInset;
// Content view has been shrunk, shrink all insets to match.
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 773823d28d95..80a75999c3d0 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -82,6 +82,14 @@ cc_library_shared_for_libandroid_runtime {
"android_util_StringBlock.cpp",
"android_util_XmlBlock.cpp",
"android_util_jar_StrictJarFile.cpp",
+ "android_view_InputDevice.cpp",
+ "android_view_KeyCharacterMap.cpp",
+ "android_view_KeyEvent.cpp",
+ "android_view_MotionEvent.cpp",
+ "android_view_Surface.cpp",
+ "android_view_VelocityTracker.cpp",
+ "android_view_VerifiedKeyEvent.cpp",
+ "android_view_VerifiedMotionEvent.cpp",
"com_android_internal_util_VirtualRefBasePtr.cpp",
"core_jni_helpers.cpp",
":deviceproductinfoconstants_aidl",
@@ -158,16 +166,11 @@ cc_library_shared_for_libandroid_runtime {
"android_view_CompositionSamplingListener.cpp",
"android_view_DisplayEventReceiver.cpp",
"android_view_InputChannel.cpp",
- "android_view_InputDevice.cpp",
"android_view_InputEventReceiver.cpp",
"android_view_InputEventSender.cpp",
"android_view_InputQueue.cpp",
- "android_view_KeyCharacterMap.cpp",
- "android_view_KeyEvent.cpp",
- "android_view_MotionEvent.cpp",
"android_view_MotionPredictor.cpp",
"android_view_PointerIcon.cpp",
- "android_view_Surface.cpp",
"android_view_SurfaceControl.cpp",
"android_view_SurfaceControlHdrLayerInfoListener.cpp",
"android_view_WindowManagerGlobal.cpp",
@@ -175,9 +178,6 @@ cc_library_shared_for_libandroid_runtime {
"android_view_SurfaceSession.cpp",
"android_view_TextureView.cpp",
"android_view_TunnelModeEnabledListener.cpp",
- "android_view_VelocityTracker.cpp",
- "android_view_VerifiedKeyEvent.cpp",
- "android_view_VerifiedMotionEvent.cpp",
"android_text_Hyphenator.cpp",
"android_os_Debug.cpp",
"android_os_GraphicsEnvironment.cpp",
@@ -394,7 +394,8 @@ cc_library_shared_for_libandroid_runtime {
"-Wno-unused-function",
],
srcs: [
- "LayoutlibLoader.cpp",
+ "platform/host/HostRuntime.cpp",
+ "platform/host/native_window_jni.cpp",
],
include_dirs: [
"external/vulkan-headers/include",
@@ -414,6 +415,7 @@ cc_library_shared_for_libandroid_runtime {
"libhostgraphics",
"libhwui",
"libimage_type_recognition",
+ "libinput",
"libjpeg",
"libpiex",
"libpng",
@@ -441,16 +443,9 @@ cc_library_shared_for_libandroid_runtime {
"android_os_MessageQueue.cpp",
"android_os_Parcel.cpp",
- "android_view_KeyCharacterMap.cpp",
- "android_view_KeyEvent.cpp",
"android_view_InputChannel.cpp",
- "android_view_InputDevice.cpp",
"android_view_InputEventReceiver.cpp",
"android_view_InputEventSender.cpp",
- "android_view_MotionEvent.cpp",
- "android_view_VelocityTracker.cpp",
- "android_view_VerifiedKeyEvent.cpp",
- "android_view_VerifiedMotionEvent.cpp",
"android_util_AssetManager.cpp",
"android_util_Binder.cpp",
@@ -458,7 +453,6 @@ cc_library_shared_for_libandroid_runtime {
"android_util_FileObserver.cpp",
],
static_libs: [
- "libinput",
"libbinderthreadstateutils",
"libsqlite",
"libgui_window_info_static",
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index b801a69638e8..3e3af40a6530 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -28,6 +28,8 @@
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedUtfChars.h>
+#include <sstream>
+
#include "android_os_Parcel.h"
#include "android_util_Binder.h"
#include "core_jni_helpers.h"
@@ -598,8 +600,8 @@ static void android_view_MotionEvent_nativeApplyTransform(JNIEnv* env, jclass cl
// ----------------- @CriticalNative ------------------------------
-static jlong android_view_MotionEvent_nativeCopy(jlong destNativePtr, jlong sourceNativePtr,
- jboolean keepHistory) {
+static jlong android_view_MotionEvent_nativeCopy(CRITICAL_JNI_PARAMS_COMMA jlong destNativePtr,
+ jlong sourceNativePtr, jboolean keepHistory) {
MotionEvent* destEvent = reinterpret_cast<MotionEvent*>(destNativePtr);
if (!destEvent) {
destEvent = new MotionEvent();
@@ -609,8 +611,8 @@ static jlong android_view_MotionEvent_nativeCopy(jlong destNativePtr, jlong sour
return reinterpret_cast<jlong>(destEvent);
}
-static jlong android_view_MotionEvent_nativeSplit(jlong destNativePtr, jlong sourceNativePtr,
- jint idBits) {
+static jlong android_view_MotionEvent_nativeSplit(CRITICAL_JNI_PARAMS_COMMA jlong destNativePtr,
+ jlong sourceNativePtr, jint idBits) {
MotionEvent* destEvent = reinterpret_cast<MotionEvent*>(destNativePtr);
if (!destEvent) {
destEvent = new MotionEvent();
@@ -621,168 +623,192 @@ static jlong android_view_MotionEvent_nativeSplit(jlong destNativePtr, jlong sou
return reinterpret_cast<jlong>(destEvent);
}
-static jint android_view_MotionEvent_nativeGetId(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getId();
}
-static jint android_view_MotionEvent_nativeGetDeviceId(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetDeviceId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getDeviceId();
}
-static jint android_view_MotionEvent_nativeGetSource(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetSource(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getSource();
}
-static void android_view_MotionEvent_nativeSetSource(jlong nativePtr, jint source) {
+static void android_view_MotionEvent_nativeSetSource(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jint source) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setSource(source);
}
-static jint android_view_MotionEvent_nativeGetDisplayId(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetDisplayId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getDisplayId();
}
-static void android_view_MotionEvent_nativeSetDisplayId(jlong nativePtr, jint displayId) {
+static void android_view_MotionEvent_nativeSetDisplayId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jint displayId) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->setDisplayId(displayId);
}
-static jint android_view_MotionEvent_nativeGetAction(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetAction(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getAction();
}
-static void android_view_MotionEvent_nativeSetAction(jlong nativePtr, jint action) {
+static void android_view_MotionEvent_nativeSetAction(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jint action) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setAction(action);
}
-static int android_view_MotionEvent_nativeGetActionButton(jlong nativePtr) {
+static int android_view_MotionEvent_nativeGetActionButton(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getActionButton();
}
-static void android_view_MotionEvent_nativeSetActionButton(jlong nativePtr, jint button) {
+static void android_view_MotionEvent_nativeSetActionButton(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr, jint button) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setActionButton(button);
}
-static jboolean android_view_MotionEvent_nativeIsTouchEvent(jlong nativePtr) {
+static jboolean android_view_MotionEvent_nativeIsTouchEvent(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->isTouchEvent();
}
-static jint android_view_MotionEvent_nativeGetFlags(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetFlags(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getFlags();
}
-static void android_view_MotionEvent_nativeSetFlags(jlong nativePtr, jint flags) {
+static void android_view_MotionEvent_nativeSetFlags(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jint flags) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setFlags(flags);
}
-static jint android_view_MotionEvent_nativeGetEdgeFlags(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetEdgeFlags(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getEdgeFlags();
}
-static void android_view_MotionEvent_nativeSetEdgeFlags(jlong nativePtr, jint edgeFlags) {
+static void android_view_MotionEvent_nativeSetEdgeFlags(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jint edgeFlags) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setEdgeFlags(edgeFlags);
}
-static jint android_view_MotionEvent_nativeGetMetaState(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetMetaState(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getMetaState();
}
-static jint android_view_MotionEvent_nativeGetButtonState(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetButtonState(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getButtonState();
}
-static void android_view_MotionEvent_nativeSetButtonState(jlong nativePtr, jint buttonState) {
+static void android_view_MotionEvent_nativeSetButtonState(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jint buttonState) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setButtonState(buttonState);
}
-static jint android_view_MotionEvent_nativeGetClassification(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetClassification(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return static_cast<jint>(event->getClassification());
}
-static void android_view_MotionEvent_nativeOffsetLocation(jlong nativePtr, jfloat deltaX,
- jfloat deltaY) {
+static void android_view_MotionEvent_nativeOffsetLocation(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jfloat deltaX, jfloat deltaY) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->offsetLocation(deltaX, deltaY);
}
-static jfloat android_view_MotionEvent_nativeGetRawXOffset(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetRawXOffset(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getRawXOffset();
}
-static jfloat android_view_MotionEvent_nativeGetRawYOffset(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetRawYOffset(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getRawYOffset();
}
-static jfloat android_view_MotionEvent_nativeGetXPrecision(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetXPrecision(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getXPrecision();
}
-static jfloat android_view_MotionEvent_nativeGetYPrecision(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetYPrecision(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getYPrecision();
}
-static jfloat android_view_MotionEvent_nativeGetXCursorPosition(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetXCursorPosition(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getXCursorPosition();
}
-static jfloat android_view_MotionEvent_nativeGetYCursorPosition(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetYCursorPosition(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getYCursorPosition();
}
-static void android_view_MotionEvent_nativeSetCursorPosition(jlong nativePtr, jfloat x, jfloat y) {
+static void android_view_MotionEvent_nativeSetCursorPosition(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr, jfloat x, jfloat y) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setCursorPosition(x, y);
}
-static jlong android_view_MotionEvent_nativeGetDownTimeNanos(jlong nativePtr) {
+static jlong android_view_MotionEvent_nativeGetDownTimeNanos(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getDownTime();
}
-static void android_view_MotionEvent_nativeSetDownTimeNanos(jlong nativePtr, jlong downTimeNanos) {
+static void android_view_MotionEvent_nativeSetDownTimeNanos(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr, jlong downTimeNanos) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->setDownTime(downTimeNanos);
}
-static jint android_view_MotionEvent_nativeGetPointerCount(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetPointerCount(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return jint(event->getPointerCount());
}
-static jint android_view_MotionEvent_nativeFindPointerIndex(jlong nativePtr, jint pointerId) {
+static jint android_view_MotionEvent_nativeFindPointerIndex(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr, jint pointerId) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return jint(event->findPointerIndex(pointerId));
}
-static jint android_view_MotionEvent_nativeGetHistorySize(jlong nativePtr) {
+static jint android_view_MotionEvent_nativeGetHistorySize(
+ CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return jint(event->getHistorySize());
}
-static void android_view_MotionEvent_nativeScale(jlong nativePtr, jfloat scale) {
+static void android_view_MotionEvent_nativeScale(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
+ jfloat scale) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
event->scale(scale);
}
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 869b53df2837..ac6298d3d0b4 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -27,15 +27,19 @@
#include <android_runtime/android_graphics_SurfaceTexture.h>
#include <android_runtime/android_view_Surface.h>
#include <android_runtime/Log.h>
+#ifdef __ANDROID__
#include <private/android/AHardwareBufferHelpers.h>
#include "android_os_Parcel.h"
#include <binder/Parcel.h>
#include <gui/BLASTBufferQueue.h>
+#endif
#include <gui/Surface.h>
+#ifdef __ANDROID__
#include <gui/SurfaceControl.h>
#include <gui/view/Surface.h>
+#endif
#include <ui/GraphicBuffer.h>
#include <ui/Rect.h>
@@ -67,6 +71,7 @@ static struct {
jfieldID bottom;
} gRectClassInfo;
+#ifdef __ANDROID__
class JNamedColorSpace {
public:
// ColorSpace.Named.SRGB.ordinal() = 0;
@@ -84,6 +89,7 @@ constexpr ui::Dataspace fromNamedColorSpaceValueToDataspace(const jint colorSpac
return ui::Dataspace::V0_SRGB;
}
}
+#endif
// ----------------------------------------------------------------------------
@@ -144,6 +150,7 @@ static inline bool isSurfaceValid(const sp<Surface>& sur) {
// ----------------------------------------------------------------------------
+#ifdef __ANDROID__
static jlong nativeCreateFromSurfaceTexture(JNIEnv* env, jclass clazz,
jobject surfaceTextureObj) {
sp<IGraphicBufferProducer> producer(SurfaceTexture_getProducer(env, surfaceTextureObj));
@@ -162,6 +169,7 @@ static jlong nativeCreateFromSurfaceTexture(JNIEnv* env, jclass clazz,
surface->incStrong(&sRefBaseOwner);
return jlong(surface.get());
}
+#endif
static void nativeRelease(JNIEnv* env, jclass clazz, jlong nativeObject) {
sp<Surface> sur(reinterpret_cast<Surface *>(nativeObject));
@@ -269,7 +277,7 @@ static void nativeAllocateBuffers(JNIEnv* /* env */ , jclass /* clazz */,
}
// ----------------------------------------------------------------------------
-
+#ifdef __ANDROID__
static jlong nativeCreateFromSurfaceControl(JNIEnv* env, jclass clazz,
jlong surfaceControlNativeObj) {
sp<SurfaceControl> ctrl(reinterpret_cast<SurfaceControl *>(surfaceControlNativeObj));
@@ -380,6 +388,7 @@ static void nativeWriteToParcel(JNIEnv* env, jclass clazz,
// to the Parcel
surfaceShim.writeToParcel(parcel, /*nameAlreadyWritten*/true);
}
+#endif
static jint nativeGetWidth(JNIEnv* env, jclass clazz, jlong nativeObject) {
Surface* surface = reinterpret_cast<Surface*>(nativeObject);
@@ -412,6 +421,7 @@ static jint nativeForceScopedDisconnect(JNIEnv *env, jclass clazz, jlong nativeO
return surface->disconnect(-1, IGraphicBufferProducer::DisconnectMode::AllLocal);
}
+#ifdef __ANDROID__
static jint nativeAttachAndQueueBufferWithColorSpace(JNIEnv* env, jclass clazz, jlong nativeObject,
jobject hardwareBuffer, jint colorSpaceId) {
Surface* surface = reinterpret_cast<Surface*>(nativeObject);
@@ -422,6 +432,7 @@ static jint nativeAttachAndQueueBufferWithColorSpace(JNIEnv* env, jclass clazz,
fromNamedColorSpaceValueToDataspace(colorSpaceId));
return err;
}
+#endif
static jint nativeSetSharedBufferModeEnabled(JNIEnv* env, jclass clazz, jlong nativeObject,
jboolean enabled) {
@@ -457,8 +468,10 @@ static void nativeDestroy(JNIEnv* env, jclass clazz, jlong nativeObject) {
// ----------------------------------------------------------------------------
static const JNINativeMethod gSurfaceMethods[] = {
+#ifdef __ANDROID__
{"nativeCreateFromSurfaceTexture", "(Landroid/graphics/SurfaceTexture;)J",
(void*)nativeCreateFromSurfaceTexture},
+#endif
{"nativeRelease", "(J)V", (void*)nativeRelease},
{"nativeIsValid", "(J)Z", (void*)nativeIsValid},
{"nativeIsConsumerRunningBehind", "(J)Z", (void*)nativeIsConsumerRunningBehind},
@@ -467,21 +480,27 @@ static const JNINativeMethod gSurfaceMethods[] = {
{"nativeUnlockCanvasAndPost", "(JLandroid/graphics/Canvas;)V",
(void*)nativeUnlockCanvasAndPost},
{"nativeAllocateBuffers", "(J)V", (void*)nativeAllocateBuffers},
+#ifdef __ANDROID__
{"nativeCreateFromSurfaceControl", "(J)J", (void*)nativeCreateFromSurfaceControl},
{"nativeGetFromSurfaceControl", "(JJ)J", (void*)nativeGetFromSurfaceControl},
{"nativeReadFromParcel", "(JLandroid/os/Parcel;)J", (void*)nativeReadFromParcel},
{"nativeWriteToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteToParcel},
+#endif
{"nativeGetWidth", "(J)I", (void*)nativeGetWidth},
{"nativeGetHeight", "(J)I", (void*)nativeGetHeight},
{"nativeGetNextFrameNumber", "(J)J", (void*)nativeGetNextFrameNumber},
{"nativeSetScalingMode", "(JI)I", (void*)nativeSetScalingMode},
{"nativeForceScopedDisconnect", "(J)I", (void*)nativeForceScopedDisconnect},
+#ifdef __ANDROID__
{"nativeAttachAndQueueBufferWithColorSpace", "(JLandroid/hardware/HardwareBuffer;I)I",
(void*)nativeAttachAndQueueBufferWithColorSpace},
+#endif
{"nativeSetSharedBufferModeEnabled", "(JZ)I", (void*)nativeSetSharedBufferModeEnabled},
{"nativeSetAutoRefreshEnabled", "(JZ)I", (void*)nativeSetAutoRefreshEnabled},
{"nativeSetFrameRate", "(JFII)I", (void*)nativeSetFrameRate},
+#ifdef __ANDROID__
{"nativeGetFromBlastBufferQueue", "(JJ)J", (void*)nativeGetFromBlastBufferQueue},
+#endif
{"nativeDestroy", "(J)V", (void*)nativeDestroy},
};
diff --git a/core/jni/include/android_runtime/android_view_Surface.h b/core/jni/include/android_runtime/android_view_Surface.h
index 637b8232105f..23cedb882aeb 100644
--- a/core/jni/include/android_runtime/android_view_Surface.h
+++ b/core/jni/include/android_runtime/android_view_Surface.h
@@ -19,6 +19,7 @@
#include <android/native_window.h>
#include <ui/PublicFormat.h>
+#include <utils/StrongPointer.h>
#include "jni.h"
diff --git a/core/jni/platform/OWNERS b/core/jni/platform/OWNERS
new file mode 100644
index 000000000000..10ce5cfece24
--- /dev/null
+++ b/core/jni/platform/OWNERS
@@ -0,0 +1,4 @@
+include /graphics/java/android/graphics/OWNERS
+
+diegoperez@google.com
+jgaillard@google.com
diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/platform/host/HostRuntime.cpp
index 83b6afa8f8f6..043385513027 100644
--- a/core/jni/LayoutlibLoader.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -80,7 +80,7 @@ int register_libcore_util_NativeAllocationRegistry_Delegate(JNIEnv* env) {
namespace android {
-extern int register_android_animation_PropertyValuesHolder(JNIEnv *env);
+extern int register_android_animation_PropertyValuesHolder(JNIEnv* env);
extern int register_android_content_AssetManager(JNIEnv* env);
extern int register_android_content_StringBlock(JNIEnv* env);
extern int register_android_content_XmlBlock(JNIEnv* env);
@@ -106,15 +106,17 @@ extern int register_android_view_MotionEvent(JNIEnv* env);
extern int register_android_view_ThreadedRenderer(JNIEnv* env);
extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env);
extern int register_android_view_VelocityTracker(JNIEnv* env);
-extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env);
+extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv* env);
-#define REG_JNI(name) { name }
+#define REG_JNI(name) \
+ { name }
struct RegJNIRec {
int (*mProc)(JNIEnv*);
};
-// Map of all possible class names to register to their corresponding JNI registration function pointer
-// The actual list of registered classes will be determined at runtime via the 'native_classes' System property
+// Map of all possible class names to register to their corresponding JNI registration function
+// pointer The actual list of registered classes will be determined at runtime via the
+// 'native_classes' System property
static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = {
{"android.animation.PropertyValuesHolder",
REG_JNI(register_android_animation_PropertyValuesHolder)},
@@ -154,8 +156,7 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = {
};
static int register_jni_procs(const std::unordered_map<std::string, RegJNIRec>& jniRegMap,
- const vector<string>& classesToRegister, JNIEnv* env) {
-
+ const vector<string>& classesToRegister, JNIEnv* env) {
for (const string& className : classesToRegister) {
if (jniRegMap.at(className).mProc(env) < 0) {
return -1;
@@ -169,15 +170,14 @@ static int register_jni_procs(const std::unordered_map<std::string, RegJNIRec>&
return 0;
}
-int AndroidRuntime::registerNativeMethods(JNIEnv* env,
- const char* className, const JNINativeMethod* gMethods, int numMethods) {
+int AndroidRuntime::registerNativeMethods(JNIEnv* env, const char* className,
+ const JNINativeMethod* gMethods, int numMethods) {
return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}
JNIEnv* AndroidRuntime::getJNIEnv() {
JNIEnv* env;
- if (javaVM->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK)
- return nullptr;
+ if (javaVM->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) return nullptr;
return env;
}
@@ -186,11 +186,10 @@ JavaVM* AndroidRuntime::getJavaVM() {
}
static vector<string> parseCsv(const string& csvString) {
- vector<string> result;
+ vector<string> result;
istringstream stream(csvString);
string segment;
- while(getline(stream, segment, ','))
- {
+ while (getline(stream, segment, ',')) {
result.push_back(segment);
}
return result;
@@ -274,7 +273,9 @@ static void* mmapFile(const char* dataFilePath) {
}
struct CloseHandleWrapper {
- void operator()(HANDLE h) { CloseHandle(h); }
+ void operator()(HANDLE h) {
+ CloseHandle(h);
+ }
};
std::unique_ptr<void, CloseHandleWrapper> mmapHandle(
CreateFileMapping(file, nullptr, PAGE_READONLY, 0, 0, nullptr));
@@ -384,8 +385,9 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
// Configuration is stored as java System properties.
// Get a reference to System.getProperty
jclass system = FindClassOrDie(env, "java/lang/System");
- jmethodID getPropertyMethod = GetStaticMethodIDOrDie(env, system, "getProperty",
- "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
+ jmethodID getPropertyMethod =
+ GetStaticMethodIDOrDie(env, system, "getProperty",
+ "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
// Java system properties that contain LayoutLib config. The initial values in the map
// are the default values if the property is not specified.
diff --git a/core/jni/platform/host/native_window_jni.cpp b/core/jni/platform/host/native_window_jni.cpp
new file mode 100644
index 000000000000..c17c480b3641
--- /dev/null
+++ b/core/jni/platform/host/native_window_jni.cpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/native_window.h>
+#include <android/native_window_jni.h>
+#include <android_runtime/android_view_Surface.h>
+#include <system/window.h>
+#include <utils/StrongPointer.h>
+
+using namespace android;
+
+ANativeWindow* ANativeWindow_fromSurface(JNIEnv* env, jobject surface) {
+ sp<ANativeWindow> win = android_view_Surface_getNativeWindow(env, surface);
+ if (win != NULL) {
+ ANativeWindow_acquire(win.get());
+ }
+ return win.get();
+}
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 5ae365cc92b4..f5bbbb4dc0a1 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -103,6 +103,7 @@ message SecureSettingsProto {
optional SettingProto accessibility_pinch_to_zoom_anywhere_enabled = 55 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto accessibility_single_finger_panning_enabled = 56 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto accessibility_floating_menu_targets = 57 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto display_daltonizer_saturation_level = 58 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Accessibility accessibility = 2;
diff --git a/core/res/res/drawable-nodpi/platlogo.xml b/core/res/res/drawable-nodpi/platlogo.xml
index f3acab063bbf..822aa22746ca 100644
--- a/core/res/res/drawable-nodpi/platlogo.xml
+++ b/core/res/res/drawable-nodpi/platlogo.xml
@@ -14,185 +14,101 @@ Copyright (C) 2021 The Android Open Source Project
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:aapt="http://schemas.android.com/aapt"
android:width="512dp"
android:height="512dp"
android:viewportWidth="512"
android:viewportHeight="512">
+ <!-- space -->
<path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0">
- <aapt:attr name="android:fillColor">
- <gradient
- android:startX="256"
- android:startY="21.81"
- android:endX="256"
- android:endY="350.42"
- android:type="linear">
- <item android:offset="0" android:color="#FF073042"/>
- <item android:offset="1" android:color="#FF073042"/>
- </gradient>
- </aapt:attr>
- </path>
+ android:pathData="M256,446.36C229.63,446.36 212.19,428.06 194.8,397.94C177.41,367.82 91.54,219.08 74.14,188.96C56.75,158.84 49.63,134.59 62.81,111.75C76,88.91 100.56,82.95 135.35,82.95C170.13,82.95 341.87,82.95 376.65,82.95C411.44,82.95 436,88.91 449.19,111.75C462.37,134.59 455.25,158.84 437.86,188.96C420.46,219.08 334.59,367.82 317.2,397.94C299.81,428.06 282.37,446.36 256,446.36H256Z"
+ android:fillColor="#202124"/>
<group>
<clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M256,446.36C229.63,446.36 212.19,428.06 194.8,397.94C177.41,367.82 91.54,219.08 74.14,188.96C56.75,158.84 49.63,134.59 62.81,111.75C76,88.91 100.56,82.95 135.35,82.95C170.13,82.95 341.87,82.95 376.65,82.95C411.44,82.95 436,88.91 449.19,111.75C462.37,134.59 455.25,158.84 437.86,188.96C420.46,219.08 334.59,367.82 317.2,397.94C299.81,428.06 282.37,446.36 256,446.36H256Z"/>
+ <!-- thrust plume -->
<path
- android:pathData="m195.27,187.64l2.25,-6.69c13.91,78.13 50.84,284.39 50.84,50.33 0,-0.97 0.72,-1.81 1.62,-1.81h12.69c0.9,0 1.62,0.83 1.62,1.8 -0.2,409.91 -69.03,-43.64 -69.03,-43.64Z"
- android:fillColor="#3ddc84"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M253,153C249.82,187.48 225.67,262.17 167.98,285.04C110.3,307.92 73.96,318.12 63,320.36L256,399L449,320.36C438.04,318.12 401.7,307.92 344.02,285.04C286.33,262.17 262.18,187.48 259,153H256H253Z"
+ android:fillColor="#C6FF00"
+ android:fillType="evenOdd"/>
<path
- android:pathData="m158.77,180.68l-33.17,57.45c-1.9,3.3 -0.77,7.52 2.53,9.42 3.3,1.9 7.52,0.77 9.42,-2.53l33.59,-58.17c54.27,24.33 116.34,24.33 170.61,0l33.59,58.17c1.97,3.26 6.21,4.3 9.47,2.33 3.17,-1.91 4.26,-5.99 2.47,-9.23l-33.16,-57.45c56.95,-30.97 95.91,-88.64 101.61,-156.76H57.17c5.7,68.12 44.65,125.79 101.61,156.76Z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M253,153C251.5,187.42 241.7,261.98 214.5,284.82C187.3,307.65 170.17,317.84 165,320.08L256,398.58L347,320.08C341.83,317.84 324.7,307.65 297.5,284.82C270.3,261.98 260.5,187.42 259,153H256H253Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
<path
- android:pathData="M250.26,187.66L262.17,187.66A2.13,2.13 0,0 1,264.3 189.78L264.3,217.85A2.13,2.13 0,0 1,262.17 219.98L250.26,219.98A2.13,2.13 0,0 1,248.14 217.85L248.14,189.78A2.13,2.13 0,0 1,250.26 187.66z"
- android:fillColor="#3ddc84"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M256,153m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0"
+ android:fillColor="#ffffff"/>
+ <!-- android head and body -->
<path
- android:pathData="M250.12,170.29L262.32,170.29A1.98,1.98 0,0 1,264.3 172.26L264.3,176.39A1.98,1.98 0,0 1,262.32 178.37L250.12,178.37A1.98,1.98 0,0 1,248.14 176.39L248.14,172.26A1.98,1.98 0,0 1,250.12 170.29z"
- android:fillColor="#3ddc84"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M151,350h199v104h-199z"
+ android:fillColor="#5F6368"/>
<path
- android:pathData="M171.92,216.82h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
+ android:pathData="M358.42,350.44C358.36,350.02 358.29,349.6 358.22,349.18C357.8,346.6 357.27,344.04 356.65,341.52C355.57,337.12 354.21,332.82 352.59,328.66C351.22,325.13 349.66,321.7 347.93,318.38C345.7,314.11 343.18,310.01 340.41,306.11C337.01,301.34 333.21,296.86 329.06,292.74C327.32,291.01 325.52,289.34 323.65,287.73C319.62,284.26 315.32,281.09 310.78,278.26C310.82,278.19 310.85,278.12 310.89,278.05C312.97,274.46 315.05,270.88 317.13,267.29C319.17,263.78 321.2,260.28 323.23,256.77C324.69,254.26 326.15,251.74 327.61,249.22C327.95,248.62 328.22,248.01 328.43,247.37C329,245.61 329.02,243.76 328.57,242.03C328.45,241.61 328.31,241.19 328.14,240.78C327.97,240.38 327.77,239.98 327.54,239.6C326.76,238.29 325.65,237.16 324.26,236.33C323.02,235.6 321.64,235.16 320.23,235.03C319.64,234.98 319.04,234.99 318.45,235.05C317.96,235.1 317.47,235.19 316.99,235.32C315.26,235.77 313.67,236.72 312.42,238.08C311.98,238.57 311.58,239.12 311.23,239.71C309.77,242.23 308.31,244.75 306.85,247.27L300.76,257.78C298.68,261.37 296.6,264.96 294.52,268.55C294.29,268.94 294.06,269.33 293.83,269.73C293.52,269.6 293.21,269.48 292.89,269.36C281.43,264.99 269,262.6 256.01,262.6C255.65,262.6 255.3,262.6 254.94,262.6C243.39,262.72 232.29,264.73 221.93,268.33C220.73,268.75 219.55,269.19 218.38,269.65C218.16,269.29 217.95,268.92 217.74,268.55C215.66,264.96 213.58,261.38 211.5,257.79C209.47,254.28 207.43,250.78 205.4,247.27C203.94,244.76 202.48,242.23 201.02,239.72C200.68,239.12 200.28,238.58 199.83,238.09C198.59,236.72 196.99,235.78 195.27,235.32C194.79,235.2 194.3,235.1 193.81,235.05C193.22,234.99 192.62,234.99 192.03,235.04C190.61,235.16 189.23,235.6 188,236.34C186.6,237.16 185.5,238.3 184.71,239.6C184.49,239.99 184.29,240.38 184.12,240.79C183.95,241.2 183.8,241.61 183.69,242.04C183.23,243.76 183.26,245.62 183.82,247.38C184.03,248.01 184.3,248.63 184.65,249.23C186.11,251.74 187.57,254.26 189.02,256.78C191.06,260.28 193.09,263.79 195.12,267.29C197.2,270.88 199.28,274.47 201.36,278.06C201.38,278.09 201.4,278.12 201.41,278.15C197.22,280.76 193.23,283.64 189.47,286.8C187.21,288.69 185.04,290.68 182.96,292.75C178.81,296.87 175.01,301.35 171.6,306.12C168.82,310.02 166.31,314.11 164.09,318.39C162.35,321.71 160.79,325.14 159.42,328.67C157.8,332.83 156.44,337.13 155.36,341.53C154.75,344.05 154.22,346.6 153.79,349.19C153.72,349.61 153.66,350.03 153.59,350.45C153.36,351.95 153.16,353.46 153,354.98L359,354.98C358.84,353.46 358.64,351.95 358.41,350.45L358.42,350.44Z"
+ android:fillColor="#5F6368"/>
</group>
+ <!-- stars -->
<group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
<path
- android:pathData="M188.8,275.73h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M131.04,134.34H127V138.38H131.04V134.34Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M369.04,337.63h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M167.04,256H163V260.04H167.04V256Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M285.93,252.22h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M373.49,127H369.45V131.04H373.49V127Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M318.96,218.84h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M292.04,226H288V230.04H292.04V226Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M294.05,288.55h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M319.04,186.91H315V190.95H319.04V186.91Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M330.82,273.31h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M355.04,222H351V226.04H355.04V222Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M188.8,298.95h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M192.04,136H188V140.04H192.04V136Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M220.14,238.94h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M336.08,196H328V204.08H336.08V196Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M272.1,318.9h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M222.04,212H218V216.04H222.04V212Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M293.34,349.25h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M163.08,175H155V183.08H163.08V175Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M161.05,254.24h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M211.08,143H203V151.08H211.08V143Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M378.92,192h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
+ android:pathData="M369.08,204H361V212.08H369.08V204Z"
+ android:fillColor="#ffffff"/>
<path
- android:pathData="M137.87,323.7h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
+ android:pathData="M169.21,204.34H161.13V212.42H169.21V204.34Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M383.04,160.07H374.95V168.15H383.04V160.07Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M192.08,183H184V191.08H192.08V183Z"
+ android:fillColor="#ffffff"/>
</group>
+ <!-- patch frame -->
<path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"
- android:strokeWidth="56.561"
+ android:pathData="M256,446.36C229.63,446.36 212.19,428.06 194.8,397.94C177.41,367.82 91.54,219.08 74.14,188.96C56.75,158.84 49.63,134.59 62.81,111.75C76,88.91 100.56,82.95 135.35,82.95C170.13,82.95 341.87,82.95 376.65,82.95C411.44,82.95 436,88.91 449.19,111.75C462.37,134.59 455.25,158.84 437.86,188.96C420.46,219.08 334.59,367.82 317.2,397.94C299.81,428.06 282.37,446.36 256,446.36H256Z"
+ android:strokeWidth="55"
android:fillColor="#00000000"
- android:strokeColor="#f86734"/>
- <path
- android:pathData="m256.22,126.57c-6.69,0 -12.12,5.27 -12.12,11.77v14.52c0,1.25 1.02,2.27 2.27,2.27h0c1.25,0 2.27,-1.02 2.27,-2.27v-3.91c0,-2.51 2.04,-4.55 4.55,-4.55h6.06c2.51,0 4.55,2.04 4.55,4.55v3.91c0,1.25 1.02,2.27 2.27,2.27s2.27,-1.02 2.27,-2.27v-14.52c0,-6.5 -5.43,-11.77 -12.12,-11.77Z"
- android:fillColor="#3ddc84"/>
+ android:strokeColor="#34A853"/>
+ <!-- text: ANDROID -->
<path
- android:pathData="m93.34,116.36l3.85,-4.36 29.64,9.76 -4.44,5.03 -6.23,-2.1 -7.86,8.91 2.86,5.92 -4.43,5.03 -13.39,-28.18ZM110.43,122.76l-8.86,-3.02 4.11,8.41 4.76,-5.39Z"
- android:fillColor="#fff"/>
+ android:pathData="M170.11,92.71C170.97,94.9 171.41,96 171.41,96C171.41,96 170.37,96 168.29,96C166.22,96 165.18,96 165.18,96C165.18,96 164.93,95.27 164.42,93.82L159.71,80.63L158.82,77.75H158.61L157.82,80.63L153.28,93.82C152.82,95.27 152.6,96 152.6,96C152.6,96 151.54,96 149.43,96C147.33,96 146.28,96 146.28,96C146.28,96 146.7,94.89 147.56,92.67L155.21,72.87C155.89,71.07 156.23,70.17 156.23,70.17C156.23,70.17 157.06,70.17 158.72,70.17C160.52,70.17 161.42,70.17 161.42,70.17C161.42,70.17 161.76,71.07 162.46,72.87L170.11,92.71ZM166.04,91.12H158.64V86.91H164.42L166.04,91.12ZM158.64,91.12H151.08L152.63,86.91H158.64V91.12ZM203.85,93.46C203.85,95.15 203.85,96 203.85,96C203.85,96 202.95,96 201.15,96C199.38,96 198.5,96 198.5,96C198.5,96 198.03,95.26 197.08,93.79L188.08,79.75H187.88L188.01,83.45V93.25C188.01,95.08 188.01,96 188.01,96C188.01,96 187.03,96 185.09,96C183.15,96 182.17,96 182.17,96C182.17,96 182.17,95.08 182.17,93.25L182.16,73.9C182.16,71.45 182.16,70.22 182.16,70.22C182.16,70.22 183.19,70.22 185.25,70.22C187.24,70.22 188.24,70.22 188.24,70.22C188.24,70.22 188.7,70.96 189.63,72.42L198.16,85.85H198.36L198.21,82.09V72.89C198.21,71.11 198.21,70.22 198.21,70.22C198.21,70.22 199.15,70.22 201.04,70.22C202.91,70.22 203.85,70.22 203.85,70.22C203.85,70.22 203.85,71.11 203.85,72.89V93.46ZM226.52,96H220.17C218.24,96 217.27,96 217.27,96C217.27,96 217.27,95.02 217.27,93.05V73.19C217.27,71.21 217.27,70.22 217.27,70.22C217.27,70.22 218.24,70.22 220.17,70.22H226.52C230.46,70.22 233.63,71.41 236.03,73.77C238.43,76.12 239.63,79.23 239.63,83.09C239.63,86.98 238.43,90.11 236.03,92.47C233.63,94.82 230.46,96 226.52,96ZM223.17,75.64V90.74H226.18C228.46,90.77 230.27,90.11 231.62,88.78C232.96,87.44 233.63,85.57 233.63,83.17C233.63,80.78 232.96,78.93 231.62,77.62C230.28,76.3 228.47,75.64 226.18,75.64H223.17ZM257.51,93.23C257.51,95.08 257.51,96 257.51,96C257.51,96 256.54,96 254.6,96C252.66,96 251.7,96 251.7,96C251.7,96 251.7,95.09 251.7,93.26V73.19C251.7,71.21 251.7,70.22 251.7,70.22C251.7,70.22 252.66,70.22 254.6,70.22H261.89C264.44,70.22 266.6,70.98 268.35,72.49C270.1,74 270.98,76.03 270.98,78.56C270.98,80.9 270.14,82.83 268.47,84.35C266.81,85.87 264.65,86.62 262.01,86.62H254.02V82.41H261.33C262.4,82.41 263.29,82.08 264.01,81.42C264.73,80.75 265.09,79.88 265.09,78.81C265.09,77.84 264.74,77.03 264.05,76.38C263.35,75.72 262.49,75.39 261.47,75.39H257.51V93.23ZM264.77,93.82L258.66,84.46L264.8,84.25L271.23,93.62C272.37,95.21 272.94,96 272.94,96C272.94,96 271.82,96 269.57,96C267.3,96 266.17,96 266.17,96C266.17,96 265.7,95.27 264.77,93.82ZM296.04,96.58C292.33,96.58 289.16,95.33 286.52,92.85C283.89,90.37 282.58,87.11 282.58,83.09C282.58,79.07 283.9,75.83 286.54,73.36C289.19,70.88 292.36,69.65 296.04,69.65C299.71,69.65 302.87,70.9 305.51,73.41C308.16,75.91 309.49,79.13 309.49,83.09C309.49,87.03 308.17,90.26 305.53,92.8C302.9,95.32 299.74,96.58 296.04,96.58ZM296.04,90.83C298.19,90.83 299.98,90.1 301.41,88.64C302.85,87.17 303.57,85.33 303.57,83.09C303.57,80.84 302.84,78.99 301.39,77.55C299.95,76.11 298.17,75.39 296.04,75.39C293.92,75.39 292.13,76.12 290.68,77.57C289.24,79.01 288.52,80.85 288.52,83.09C288.52,85.35 289.23,87.2 290.66,88.66C292.1,90.11 293.89,90.83 296.04,90.83ZM327.64,93.05C327.64,95.02 327.64,96 327.64,96C327.64,96 326.63,96 324.61,96C322.59,96 321.57,96 321.57,96C321.57,96 321.57,95.02 321.57,93.05V73.18C321.57,71.21 321.57,70.22 321.57,70.22C321.57,70.22 322.58,70.22 324.6,70.22C326.63,70.22 327.64,70.22 327.64,70.22C327.64,70.22 327.64,71.21 327.64,73.18V93.05ZM350.31,96H343.96C342.03,96 341.06,96 341.06,96C341.06,96 341.06,95.02 341.06,93.05V73.19C341.06,71.21 341.06,70.22 341.06,70.22C341.06,70.22 342.03,70.22 343.96,70.22H350.31C354.25,70.22 357.42,71.41 359.82,73.77C362.22,76.12 363.42,79.23 363.42,83.09C363.42,86.98 362.22,90.11 359.82,92.47C357.42,94.82 354.25,96 350.31,96ZM346.96,75.64V90.74H349.97C352.25,90.77 354.06,90.11 355.41,88.78C356.75,87.44 357.42,85.57 357.42,83.17C357.42,80.78 356.75,78.93 355.41,77.62C354.07,76.3 352.26,75.64 349.97,75.64H346.96Z"
+ android:fillColor="#E9F3EB"/>
+ <!-- text: 15 -->
<path
- android:pathData="m153.62,100.85l-21.71,-6.2 10.38,14.38 -5.21,3.76 -16.78,-23.26 4.49,-3.24 21.65,6.19 -10.35,-14.35 5.24,-3.78 16.78,23.26 -4.49,3.24Z"
- android:fillColor="#fff"/>
+ android:pathData="M236.59,363.25C236.59,365.75 236.59,367 236.59,367C236.59,367 235.32,367 232.79,367C230.32,367 229.09,367 229.09,367C229.09,367 229.09,365.75 229.09,363.25V305.85L216.64,314.75C215,315.92 214.19,316.5 214.19,316.5C214.19,316.5 213.54,315.5 212.24,313.5C210.97,311.6 210.34,310.65 210.34,310.65C210.34,310.62 211.2,309.98 212.94,308.75L227.64,298.2C230.3,296.23 231.64,295.25 231.64,295.25C231.64,295.25 232.1,295.25 233.04,295.25C235.4,295.25 236.59,295.25 236.59,295.25C236.59,295.25 236.59,296.47 236.59,298.9V363.25ZM247.09,330L251.19,299C251.52,296.6 251.69,295.4 251.69,295.4C251.69,295.4 252.77,295.4 254.94,295.4H284.54C286.97,295.4 288.19,295.4 288.19,295.4C288.19,295.4 288.19,296.58 288.19,298.95C288.19,301.48 288.19,302.75 288.19,302.75C288.19,302.75 286.97,302.75 284.54,302.75H257.49L254.19,327.45L254.39,327.5C256.09,325.77 258.27,324.38 260.94,323.35C263.61,322.28 266.65,321.75 270.09,321.75C276.55,321.75 281.99,323.97 286.39,328.4C290.79,332.8 292.99,338.32 292.99,344.95C292.99,351.75 290.8,357.4 286.44,361.9C282.11,366.37 276.42,368.6 269.39,368.6C263.09,368.6 257.77,367 253.44,363.8C249.1,360.6 246.26,356.85 244.89,352.55C244.26,350.32 243.94,349.2 243.94,349.2C243.94,349.2 245.09,348.77 247.39,347.9C249.79,347 250.99,346.55 250.99,346.55C250.99,346.55 251.3,347.73 251.94,350.1C252.8,352.73 254.71,355.27 257.64,357.7C260.61,360.13 264.44,361.35 269.14,361.35C274.27,361.35 278.24,359.88 281.04,356.95C283.84,353.98 285.24,350.03 285.24,345.1C285.24,340.37 283.67,336.52 280.54,333.55C277.44,330.58 273.4,329.1 268.44,329.1C265.47,329.1 262.95,329.52 260.89,330.35C258.82,331.15 257.09,332.28 255.69,333.75C254.39,335.25 253.74,336 253.74,336C253.74,336 252.55,335.52 250.19,334.55C247.85,333.62 246.69,333.15 246.69,333.15C246.69,333.15 246.82,332.1 247.09,330Z"
+ android:fillColor="#E9F3EB"/>
+ <!-- spacecraft -->
<path
- android:pathData="m161.46,63.15l8.99,-3.84c7.43,-3.18 15.96,0.12 19.09,7.44 3.13,7.32 -0.38,15.76 -7.81,18.94l-8.99,3.84 -11.28,-26.38ZM179.41,80.26c4.46,-1.91 5.96,-6.72 4.15,-10.96 -1.81,-4.24 -6.33,-6.48 -10.79,-4.57l-3.08,1.32 6.64,15.53 3.08,-1.32Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="m204.23,47.57l11.1,-2.2c5.31,-1.05 9.47,2.08 10.4,6.76 0.72,3.65 -0.76,6.37 -4.07,8.34l12.4,10.44 -7.57,1.5 -11.65,-9.76 -1.03,0.2 2.3,11.61 -6.3,1.25 -5.57,-28.14ZM216.78,56.7c1.86,-0.37 3,-1.71 2.68,-3.33 -0.34,-1.7 -1.88,-2.43 -3.74,-2.06l-4.04,0.8 1.07,5.39 4.04,-0.8Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="m244.29,55.6c0.13,-8.16 6.86,-14.72 15.06,-14.58 8.16,0.13 14.72,6.9 14.58,15.06s-6.91,14.72 -15.06,14.58c-8.2,-0.13 -14.71,-6.9 -14.58,-15.06ZM267.44,55.98c0.08,-4.64 -3.54,-8.66 -8.18,-8.74 -4.68,-0.08 -8.42,3.82 -8.5,8.47 -0.08,4.65 3.54,8.66 8.22,8.74 4.64,0.08 8.39,-3.82 8.46,-8.47Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="m294.39,44.84l6.31,1.23 -5.49,28.16 -6.31,-1.23 5.49,-28.16Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="m321.94,51.41l9.14,3.48c7.55,2.88 11.39,11.17 8.56,18.61 -2.83,7.44 -11.22,11.07 -18.77,8.19l-9.14,-3.48 10.22,-26.8ZM322.96,76.19c4.53,1.73 8.95,-0.69 10.6,-5 1.64,-4.3 -0.05,-9.06 -4.58,-10.78l-3.13,-1.19 -6.01,15.78 3.13,1.19Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="m381.41,89.24l-4.21,-3.21 3.65,-4.78 9.06,6.91 -17.4,22.81 -4.85,-3.7 13.75,-18.02Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="m397.96,126.37l-9.56,-10.26 3.61,-3.36 22.8,-1.25 3.74,4.02 -12.35,11.51 2.51,2.69 -4.08,3.8 -2.51,-2.69 -4.55,4.24 -4.16,-4.46 4.55,-4.24ZM407.83,117.17l-10.28,0.58 4.49,4.82 5.79,-5.4Z"
- android:fillColor="#fff"/>
+ android:pathData="M256.12,121C249.43,121 244,126.27 244,132.77V147.29C244,148.54 245.02,149.56 246.27,149.56C247.53,149.56 248.55,148.55 248.55,147.29V143.38C248.55,140.87 250.58,138.83 253.09,138.83H259.15C261.66,138.83 263.7,140.87 263.7,143.38V147.29C263.7,148.54 264.71,149.56 265.97,149.56C267.23,149.56 268.24,148.55 268.24,147.29V132.77C268.24,126.27 262.82,121 256.12,121H256.12Z"
+ android:fillColor="#E9F3EB"/>
</vector>
-
diff --git a/core/res/res/drawable-nodpi/stat_sys_adb.xml b/core/res/res/drawable-nodpi/stat_sys_adb.xml
index 53a6836b4367..8ae2a9b40bc7 100644
--- a/core/res/res/drawable-nodpi/stat_sys_adb.xml
+++ b/core/res/res/drawable-nodpi/stat_sys_adb.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2023 The Android Open Source Project
+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.
@@ -19,36 +19,20 @@ Copyright (C) 2023 The Android Open Source Project
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
+ <group>
+ <clip-path
+ android:pathData="M12,20.923C10.764,20.923 9.946,20.065 9.131,18.653C8.316,17.241 4.291,10.269 3.476,8.857C2.66,7.445 2.326,6.309 2.944,5.238C3.563,4.167 4.714,3.888 6.344,3.888C7.975,3.888 16.025,3.888 17.656,3.888C19.286,3.888 20.437,4.167 21.056,5.238C21.674,6.309 21.34,7.445 20.524,8.857C19.709,10.269 15.684,17.241 14.869,18.653C14.054,20.065 13.236,20.923 12,20.923H12Z"/>
<path
- android:name="ring"
- android:pathData="M 12 21 C 16.971 21 21 16.971 21 12 C 21 7.029 16.971 3 12 3 C 7.029 3 3 7.029 3 12 C 3 16.971 7.029 21 12 21 Z"
- android:fillColor="#00000000"
- android:strokeColor="#ffffff"
- android:strokeWidth="2"/>
- <group android:name="group">
- <clip-path
- android:pathData="M 20.5 12 C 20.5 16.694 16.694 20.5 12 20.5 C 7.306 20.5 3.5 16.694 3.5 12 C 3.5 7.306 7.306 3.5 12 3.5 C 16.694 3.5 20.5 7.306 20.5 12 Z"/>
- <path
- android:pathData="M 14.812 9.023 C 13.014 9.707 11.027 9.707 9.229 9.023 L 8.265 10.693 C 8.06 11.048 7.605 11.17 7.25 10.964 C 6.895 10.759 6.773 10.305 6.978 9.949 L 7.899 8.355 C 5.988 7.137 4.702 5.084 4.502 2.695 L 4.456 2.153 L 19.584 2.153 L 19.539 2.695 C 19.339 5.084 18.052 7.137 16.142 8.355 L 17.067 9.958 C 17.259 10.307 17.142 10.746 16.801 10.952 C 16.45 11.165 15.993 11.052 15.781 10.701 L 15.775 10.693 L 14.812 9.023 Z"
- android:fillColor="#ffffff"/>
- <group android:name="stars">
- <path android:pathData="
- M 7,14 h1v1h-1z
- M 13,15 h1v1h-1z
- M 14,11 h1v1h-1z
-
- M 11,17 h0.5v0.5h-0.5z
- M 10,15 h0.5v0.5h-0.5z
- M 13,18 h0.5v0.5h-0.5z
- M 17,15 h0.5v0.5h-0.5z
- M 15,14 h0.5v0.5h-0.5z
- M 18,12 h0.5v0.5h-0.5z
- M 5,13 h0.5v0.5h-0.5z
- M 5,10 h0.5v0.5h-0.5z
- M 9,11 h0.5v0.5h-0.5z
- M 8,17 h0.5v0.5h-0.5z
- M 12,12 h0.5v0.5h-0.5z
- " android:fillColor="#ffffff"/>
- </group>
- </group>
+ android:pathData="M5,14.978h14v9.8h-14z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M18.722,15.576C18.717,15.548 18.713,15.521 18.708,15.493C18.68,15.324 18.646,15.156 18.605,14.991C18.534,14.701 18.445,14.42 18.339,14.146C18.249,13.915 18.146,13.69 18.033,13.472C17.886,13.192 17.722,12.923 17.539,12.667C17.316,12.354 17.067,12.06 16.795,11.789C16.68,11.676 16.562,11.566 16.44,11.461C16.175,11.233 15.893,11.025 15.595,10.839C15.598,10.834 15.6,10.83 15.602,10.825C15.739,10.59 15.875,10.355 16.012,10.119C16.145,9.889 16.279,9.659 16.412,9.429C16.508,9.264 16.604,9.098 16.699,8.933C16.722,8.894 16.74,8.854 16.753,8.812C16.791,8.696 16.792,8.575 16.762,8.462C16.754,8.434 16.745,8.406 16.734,8.38C16.723,8.353 16.71,8.327 16.695,8.302C16.644,8.216 16.571,8.142 16.479,8.087C16.399,8.039 16.308,8.011 16.215,8.002C16.176,7.999 16.137,7.999 16.098,8.003C16.066,8.007 16.034,8.013 16.002,8.021C15.889,8.051 15.785,8.113 15.703,8.202C15.674,8.235 15.647,8.27 15.624,8.309C15.529,8.475 15.433,8.64 15.337,8.805L14.937,9.495C14.801,9.731 14.664,9.966 14.528,10.202C14.513,10.227 14.498,10.253 14.483,10.279C14.462,10.271 14.442,10.263 14.421,10.255C13.669,9.968 12.853,9.811 12,9.811C11.977,9.811 11.954,9.811 11.931,9.811C11.172,9.819 10.444,9.951 9.764,10.188C9.686,10.215 9.608,10.244 9.531,10.274C9.517,10.25 9.503,10.226 9.489,10.202C9.353,9.966 9.216,9.731 9.08,9.495C8.946,9.265 8.813,9.035 8.679,8.805C8.584,8.64 8.488,8.475 8.392,8.31C8.37,8.271 8.343,8.235 8.314,8.203C8.232,8.113 8.127,8.051 8.014,8.021C7.983,8.013 7.951,8.007 7.919,8.004C7.88,8 7.841,7.999 7.802,8.003C7.709,8.011 7.618,8.039 7.537,8.088C7.446,8.142 7.373,8.217 7.322,8.302C7.307,8.327 7.294,8.353 7.283,8.38C7.271,8.407 7.262,8.434 7.255,8.462C7.225,8.575 7.226,8.697 7.264,8.812C7.277,8.854 7.295,8.894 7.318,8.934C7.413,9.099 7.509,9.264 7.605,9.429C7.738,9.659 7.872,9.889 8.005,10.119C8.141,10.355 8.278,10.59 8.414,10.826C8.415,10.828 8.417,10.83 8.418,10.832C8.143,11.003 7.881,11.192 7.634,11.4C7.486,11.524 7.343,11.654 7.207,11.79C6.935,12.061 6.685,12.354 6.462,12.668C6.279,12.923 6.114,13.192 5.968,13.472C5.855,13.691 5.752,13.915 5.662,14.147C5.556,14.42 5.467,14.702 5.396,14.991C5.355,15.157 5.321,15.324 5.293,15.494C5.288,15.521 5.284,15.549 5.279,15.576C5.264,15.675 5.251,15.774 5.241,15.874L18.759,15.874C18.749,15.774 18.736,15.675 18.72,15.576L18.722,15.576Z"
+ android:fillColor="#ffffff"/>
+ </group>
+ <path
+ android:pathData="M12,20.923C10.764,20.923 9.946,20.065 9.131,18.653C8.316,17.241 4.291,10.269 3.476,8.857C2.66,7.445 2.326,6.309 2.944,5.238C3.563,4.167 4.714,3.888 6.344,3.888C7.975,3.888 16.025,3.888 17.656,3.888C19.286,3.888 20.437,4.167 21.056,5.238C21.674,6.309 21.34,7.445 20.524,8.857C19.709,10.269 15.684,17.241 14.869,18.653C14.054,20.065 13.236,20.923 12,20.923H12Z"
+ android:strokeWidth="2"
+ android:fillColor="#00000000"
+ android:strokeColor="#ffffff"/>
</vector>
+
diff --git a/core/res/res/layout/list_menu_item_icon.xml b/core/res/res/layout/list_menu_item_icon.xml
index d8514608e8dd..a30be6a13db6 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
deleted file mode 100644
index e20ead62032c..000000000000
--- a/core/res/res/layout/popup_menu_item_layout_material.xml
+++ /dev/null
@@ -1,91 +0,0 @@
-<?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/attrs.xml b/core/res/res/values/attrs.xml
index b262ebd3feea..0df49dc6e0e8 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2300,7 +2300,7 @@
ensure that the status bar has enough contrast with the contents of this app, and set
an appropriate effective bar background accordingly.
See: {@link android.R.attr#enforceStatusBarContrast}
- <p>If the app targets
+ <p>If the window belongs to an app targeting
{@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above,
this attribute is ignored.
@deprecated Draw proper background behind
@@ -2320,7 +2320,7 @@
ensure that the navigation bar has enough contrast with the contents of this app, and
set an appropriate effective bar background accordingly.
See: {@link android.R.attr#enforceNavigationBarContrast}
- <p>If the app targets
+ <p>If the window belongs to an app targeting
{@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above,
this attribute is ignored.
@deprecated Draw proper background behind
@@ -2335,7 +2335,7 @@
have been requested to be translucent with
{@link android.R.attr#windowTranslucentNavigation}.
Corresponds to {@link android.view.Window#setNavigationBarDividerColor(int)}.
- <p>If the app targets
+ <p>If the window belongs to an app targeting
{@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above,
this attribute is ignored.
@deprecated Draw proper background behind
@@ -2431,7 +2431,9 @@
<!-- Controls how the window is laid out if there is a {@code DisplayCutout}.
<p>
- Defaults to {@code default}.
+ Defaults to {@code default}. But if the window fills the screen, and it belongs to an app
+ targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or
+ above, the behavior will be the same as specifying {@code always} regardless.
<p>
See also
{@link android.view.WindowManager.LayoutParams#layoutInDisplayCutoutMode
@@ -2528,8 +2530,8 @@
<!-- Flag indicating whether this window would opt-out the edge-to-edge enforcement.
- <p>If this is false, the edge-to-edge enforcement will be applied to the window if its
- app targets
+ <p>If this is false, the edge-to-edge enforcement will be applied to the window if it
+ belongs to an app targeting
{@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above.
The affected behaviors are:
<ul>
@@ -2537,9 +2539,8 @@
through the {@link android.view.WindowInsets} to the content view, as if calling
{@link android.view.Window#setDecorFitsSystemWindows(boolean)} with false.
<li>{@link android.view.WindowManager.LayoutParams#layoutInDisplayCutoutMode} of
- the non-floating windows will be set to {@link
+ the fill-screen windows will behave as specifying {@link
android.view.WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS}.
- Changing it to other values will cause {@link lang.IllegalArgumentException}.
<li>The framework will set {@link android.R.attr#statusBarColor},
{@link android.R.attr#navigationBarColor}, and
{@link android.R.attr#navigationBarDividerColor} to transparent.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index a622d36edc6a..1a618700cd17 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3009,6 +3009,10 @@
<!-- Maximum number of users we allow to be running at a time -->
<integer name="config_multiuserMaxRunningUsers">3</integer>
+ <!-- Number of seconds of uptime after a full user enters the background before we attempt to
+ stop it due to inactivity. Set to -1 to disable scheduling stopping background users. -->
+ <integer name="config_backgroundUserScheduledStopTimeSecs">1800</integer> <!-- 30 minutes -->
+
<!-- Whether to delay user data locking for background user.
If false, user switched-out from user switching will still be in running state until
config_multiuserMaxRunningUsers is reached. Once config_multiuserMaxRunningUsers is
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 7c9f2efa9b2d..a248edea375c 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -235,6 +235,12 @@
<integer name="config_esim_bootstrap_data_limit_bytes">-1</integer>
<java-symbol type="integer" name="config_esim_bootstrap_data_limit_bytes" />
+ <!-- Reevaluate existing data networks for bootstrap sim data usage at mentioned intervals
+ during esim bootstrap activation. If the value set is 0 or -1, default interval of
+ 60000 millis will be set. -->
+ <integer name="config_reevaluate_bootstrap_sim_data_usage_millis">60000</integer>
+ <java-symbol type="integer" name="config_reevaluate_bootstrap_sim_data_usage_millis" />
+
<!-- Telephony config for the PLMNs of all satellite providers. This is used by satellite modem
to identify providers that should be ignored if the carrier config
carrier_supported_satellite_services_per_provider_bundle does not support them.
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c4033f2d680a..ead582721826 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -488,6 +488,7 @@
<java-symbol type="integer" name="config_lockSoundVolumeDb" />
<java-symbol type="integer" name="config_multiuserMaximumUsers" />
<java-symbol type="integer" name="config_multiuserMaxRunningUsers" />
+ <java-symbol type="integer" name="config_backgroundUserScheduledStopTimeSecs" />
<java-symbol type="bool" name="config_multiuserDelayUserDataLocking" />
<java-symbol type="bool" name="config_multiuserVisibleBackgroundUsers" />
<java-symbol type="bool" name="config_multiuserVisibleBackgroundUsersOnDefaultDisplay" />
@@ -1527,7 +1528,6 @@
<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" />
diff --git a/core/tests/FileSystemUtilsTest/TEST_MAPPING b/core/tests/FileSystemUtilsTest/TEST_MAPPING
index 89b3a7acc10c..d41e981cbd67 100644
--- a/core/tests/FileSystemUtilsTest/TEST_MAPPING
+++ b/core/tests/FileSystemUtilsTest/TEST_MAPPING
@@ -1,5 +1,5 @@
{
- "postsubmit": [
+ "presubmit": [
{
"name": "FileSystemUtilsTests"
}
diff --git a/core/tests/coretests/src/android/app/ActivityManagerTest.java b/core/tests/coretests/src/android/app/ActivityManagerTest.java
index d930e4d79a3a..3c042bac895d 100644
--- a/core/tests/coretests/src/android/app/ActivityManagerTest.java
+++ b/core/tests/coretests/src/android/app/ActivityManagerTest.java
@@ -78,6 +78,6 @@ public class ActivityManagerTest {
public void testRestrictionLevel() throws Exception {
// For the moment mostly want to confirm we don't crash
assertNotNull(ActivityManager.restrictionLevelToName(
- ActivityManager.RESTRICTION_LEVEL_HIBERNATION));
+ ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED));
}
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
index 8506905e6ca0..f8c2d6aee84a 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
@@ -23,15 +23,19 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat
import static com.android.window.flags.Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.app.Activity;
+import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
@@ -45,6 +49,7 @@ import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.view.DisplayInfo;
import android.window.ActivityWindowInfo;
+import android.window.WindowTokenClient;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -55,6 +60,7 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -180,4 +186,36 @@ public class ClientTransactionListenerControllerTest {
verify(mActivityWindowInfoListener, never()).accept(any(), any());
}
+
+ @Test
+ public void testWindowTokenClient_onConfigurationChanged() {
+ doNothing().when(mController).onContextConfigurationPreChanged(any());
+ doNothing().when(mController).onContextConfigurationPostChanged(any());
+
+ final WindowTokenClient windowTokenClient = spy(new WindowTokenClient());
+ final Context context = mock(Context.class);
+ windowTokenClient.attachContext(context);
+
+ doReturn(mController).when(windowTokenClient).getClientTransactionListenerController();
+ doNothing().when(windowTokenClient).onConfigurationChangedInner(any(), any(), anyInt(),
+ anyBoolean());
+
+ // Not trigger when shouldReportConfigChange is false.
+ windowTokenClient.onConfigurationChanged(mConfiguration, 123 /* newDisplayId */,
+ false /* shouldReportConfigChange*/);
+
+ verify(mController, never()).onContextConfigurationPreChanged(any());
+ verify(mController, never()).onContextConfigurationPostChanged(any());
+
+ // Trigger in order when shouldReportConfigChange is true.
+ clearInvocations(windowTokenClient);
+ final InOrder inOrder = inOrder(mController, windowTokenClient);
+ windowTokenClient.onConfigurationChanged(mConfiguration, 123 /* newDisplayId */,
+ true /* shouldReportConfigChange*/);
+
+ inOrder.verify(mController).onContextConfigurationPreChanged(context);
+ inOrder.verify(windowTokenClient).onConfigurationChangedInner(context, mConfiguration,
+ 123 /* newDisplayId */, true /* shouldReportConfigChange*/);
+ inOrder.verify(mController).onContextConfigurationPostChanged(context);
+ }
}
diff --git a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
index 3a872b50af75..5bf88da1b3bb 100644
--- a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
@@ -28,7 +28,9 @@ import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -97,7 +99,7 @@ public class FaceManagerTest {
mLooper = new TestLooper();
mHandler = new Handler(mLooper.getLooper());
- when(mContext.getMainLooper()).thenReturn(mLooper.getLooper());
+ when(mContext.getMainThreadHandler()).thenReturn(mHandler);
when(mContext.getOpPackageName()).thenReturn(PACKAGE_NAME);
when(mContext.getAttributionTag()).thenReturn(ATTRIBUTION_TAG);
when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
@@ -210,6 +212,39 @@ public class FaceManagerTest {
verify(mFaceDetectionCallback).onDetectionError(anyInt());
}
+ @Test
+ public void authenticate_onErrorCanceled() throws RemoteException {
+ final FaceManager.AuthenticationCallback authenticationCallback1 = mock(
+ FaceManager.AuthenticationCallback.class);
+ final FaceManager.AuthenticationCallback authenticationCallback2 = mock(
+ FaceManager.AuthenticationCallback.class);
+
+ final ArgumentCaptor<IFaceServiceReceiver> faceServiceReceiverArgumentCaptor =
+ ArgumentCaptor.forClass(IFaceServiceReceiver.class);
+
+ mFaceManager.authenticate(null, new CancellationSignal(),
+ authenticationCallback1, mHandler,
+ new FaceAuthenticateOptions.Builder().build());
+ mFaceManager.authenticate(null, new CancellationSignal(),
+ authenticationCallback2, mHandler,
+ new FaceAuthenticateOptions.Builder().build());
+
+ verify(mService, times(2)).authenticate(any(IBinder.class), eq(0L),
+ faceServiceReceiverArgumentCaptor.capture(), any());
+
+ final List<IFaceServiceReceiver> faceServiceReceivers =
+ faceServiceReceiverArgumentCaptor.getAllValues();
+ faceServiceReceivers.get(0).onError(5 /* error */, 0 /* vendorCode */);
+ mLooper.dispatchAll();
+
+ verify(authenticationCallback1).onAuthenticationError(eq(5), anyString());
+ verify(authenticationCallback2, never()).onAuthenticationError(anyInt(), anyString());
+
+ faceServiceReceivers.get(1).onError(5 /* error */, 0 /* vendorCode */);
+ mLooper.dispatchAll();
+ verify(authenticationCallback2).onAuthenticationError(eq(5), anyString());
+ }
+
private void initializeProperties() throws RemoteException {
verify(mService).addAuthenticatorsRegisteredCallback(mCaptor.capture());
diff --git a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
index ce7d6a95c2f4..c3ea7d38e2f1 100644
--- a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
@@ -26,7 +26,9 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -93,6 +95,7 @@ public class FingerprintManagerTest {
mHandler = new Handler(mLooper.getLooper());
when(mContext.getMainLooper()).thenReturn(mLooper.getLooper());
+ when(mContext.getMainThreadHandler()).thenReturn(mHandler);
when(mContext.getOpPackageName()).thenReturn(PACKAGE_NAME);
when(mContext.getAttributionTag()).thenReturn(ATTRIBUTION_TAG);
when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
@@ -187,4 +190,38 @@ public class FingerprintManagerTest {
verify(mFingerprintDetectionCallback).onDetectionError(anyInt());
}
+
+ @Test
+ public void authenticate_onErrorCanceled() throws RemoteException {
+ final FingerprintManager.AuthenticationCallback authenticationCallback1 = mock(
+ FingerprintManager.AuthenticationCallback.class);
+ final FingerprintManager.AuthenticationCallback authenticationCallback2 = mock(
+ FingerprintManager.AuthenticationCallback.class);
+
+ final ArgumentCaptor<IFingerprintServiceReceiver> fingerprintServiceReceiverArgumentCaptor =
+ ArgumentCaptor.forClass(IFingerprintServiceReceiver.class);
+
+ mFingerprintManager.authenticate(null, new CancellationSignal(),
+ authenticationCallback1, mHandler,
+ new FingerprintAuthenticateOptions.Builder().build());
+ mFingerprintManager.authenticate(null, new CancellationSignal(),
+ authenticationCallback2, mHandler,
+ new FingerprintAuthenticateOptions.Builder().build());
+
+ verify(mService, times(2)).authenticate(any(IBinder.class), eq(0L),
+ fingerprintServiceReceiverArgumentCaptor.capture(), any());
+
+ final List<IFingerprintServiceReceiver> fingerprintServiceReceivers =
+ fingerprintServiceReceiverArgumentCaptor.getAllValues();
+ fingerprintServiceReceivers.get(0).onError(5 /* error */, 0 /* vendorCode */);
+ mLooper.dispatchAll();
+
+ verify(authenticationCallback1).onAuthenticationError(eq(5), anyString());
+ verify(authenticationCallback2, never()).onAuthenticationError(anyInt(), anyString());
+
+ fingerprintServiceReceivers.get(1).onError(5 /* error */, 0 /* vendorCode */);
+ mLooper.dispatchAll();
+
+ verify(authenticationCallback2).onAuthenticationError(eq(5), anyString());
+ }
}
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 80fef6ca801e..ccebd0336ea6 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -1258,18 +1258,6 @@ public class ViewRootImplTest {
sInstrumentation.waitForIdleSync();
mViewRootImpl = mView.getViewRootImpl();
- waitForFrameRateCategoryToSettle(mView);
-
- sInstrumentation.runOnMainSync(() -> {
- assertEquals(FRAME_RATE_CATEGORY_DEFAULT,
- mViewRootImpl.getPreferredFrameRateCategory());
- mView.invalidate();
- int expected = toolkitFrameRateDefaultNormalReadOnly()
- ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH;
- runAfterDraw(() -> assertEquals(expected,
- mViewRootImpl.getLastPreferredFrameRateCategory()));
- });
- waitForAfterDraw();
waitForFrameRateCategoryToSettle(mView);
diff --git a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
index 82251b841c6d..4921e4a4a061 100644
--- a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
+++ b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
@@ -20,7 +20,6 @@ import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_M
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
@@ -82,14 +81,8 @@ public final class PhoneWindowTest {
createPhoneWindowWithTheme(R.style.LayoutInDisplayCutoutModeUnset);
installDecor();
- if ((mPhoneWindow.getAttributes().privateFlags & PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED) != 0
- && !mPhoneWindow.isFloating()) {
- assertThat(mPhoneWindow.getAttributes().layoutInDisplayCutoutMode,
- is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS));
- } else {
- assertThat(mPhoneWindow.getAttributes().layoutInDisplayCutoutMode,
- is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT));
- }
+ assertThat(mPhoneWindow.getAttributes().layoutInDisplayCutoutMode,
+ is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT));
}
@Test
diff --git a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
index 8d66cfc7a3dc..1dbb7758bf39 100644
--- a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
@@ -29,6 +29,7 @@ import static org.junit.Assert.assertThat;
import android.content.Context;
import android.graphics.Insets;
import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
import android.view.DisplayCutout;
import android.view.View;
import android.view.View.OnApplyWindowInsetsListener;
@@ -49,6 +50,7 @@ import java.lang.reflect.Field;
@RunWith(AndroidJUnit4.class)
@SmallTest
+@Presubmit
public class ActionBarOverlayLayoutTest {
private static final Insets TOP_INSET_5 = Insets.of(0, 5, 0, 0);
@@ -167,10 +169,67 @@ public class ActionBarOverlayLayoutTest {
assertThat(mContentInsetsListener.captured, is(insetsWith(Insets.NONE, NO_CUTOUT)));
}
+ @Test
+ public void topInset_cutout_noContentOnApplyWindowInsetsListener() {
+ mLayout.setHasContentOnApplyWindowInsetsListener(false);
+ mLayout.dispatchApplyWindowInsets(insetsWith(TOP_INSET_5, CUTOUT_5));
+
+ assertThat(mContentInsetsListener.captured, nullValue());
+
+ mLayout.measure(EXACTLY_1000, EXACTLY_1000);
+
+ // Action bar height is added to the top inset
+ assertThat(mContentInsetsListener.captured, is(insetsWith(TOP_INSET_25, CUTOUT_5)));
+ }
+
+ @Test
+ public void topInset_cutout__hasContentOnApplyWindowInsetsListener() {
+ mLayout.setHasContentOnApplyWindowInsetsListener(true);
+ mLayout.dispatchApplyWindowInsets(insetsWith(TOP_INSET_5, CUTOUT_5));
+
+ assertThat(mContentInsetsListener.captured, nullValue());
+
+ mLayout.measure(EXACTLY_1000, EXACTLY_1000);
+
+ assertThat(mContentInsetsListener.captured, is(insetsWith(Insets.NONE, NO_CUTOUT)));
+ }
+
+ @Test
+ public void topInset_noCutout_noContentOnApplyWindowInsetsListener() {
+ mLayout.setHasContentOnApplyWindowInsetsListener(false);
+ mLayout.dispatchApplyWindowInsets(insetsWith(TOP_INSET_5, NO_CUTOUT));
+
+ assertThat(mContentInsetsListener.captured, nullValue());
+
+ mLayout.measure(EXACTLY_1000, EXACTLY_1000);
+
+ // Action bar height is added to the top inset
+ assertThat(mContentInsetsListener.captured, is(insetsWith(TOP_INSET_25, NO_CUTOUT)));
+ }
+
+ @Test
+ public void topInset_noCutout__hasContentOnApplyWindowInsetsListener() {
+ mLayout.setHasContentOnApplyWindowInsetsListener(true);
+ mLayout.dispatchApplyWindowInsets(insetsWith(TOP_INSET_5, NO_CUTOUT));
+
+ assertThat(mContentInsetsListener.captured, nullValue());
+
+ mLayout.measure(EXACTLY_1000, EXACTLY_1000);
+
+ assertThat(mContentInsetsListener.captured, is(insetsWith(Insets.NONE, NO_CUTOUT)));
+ }
+
private WindowInsets insetsWith(Insets content, DisplayCutout cutout) {
- return new WindowInsets(WindowInsets.createCompatTypeMap(content.toRect()), null, null,
- false, 0, 0, cutout, null, null, null, WindowInsets.Type.systemBars(), false,
- null, null, 0, 0);
+ final Insets cutoutInsets = cutout != null
+ ? Insets.of(cutout.getSafeInsets())
+ : Insets.NONE;
+ return new WindowInsets.Builder()
+ .setSystemWindowInsets(content)
+ .setDisplayCutout(cutout)
+ .setInsets(WindowInsets.Type.displayCutout(), cutoutInsets)
+ .setInsetsIgnoringVisibility(WindowInsets.Type.displayCutout(), cutoutInsets)
+ .setVisible(WindowInsets.Type.displayCutout(), true)
+ .build();
}
private ViewGroup createViewGroupWithId(int id) {
@@ -181,14 +240,16 @@ public class ActionBarOverlayLayoutTest {
static class TestActionBarOverlayLayout extends ActionBarOverlayLayout {
private boolean mStable;
+ private boolean mHasContentOnApplyWindowInsetsListener;
public TestActionBarOverlayLayout(Context context) {
super(context);
+ mHasContentOnApplyWindowInsetsListener = true;
}
@Override
public WindowInsets computeSystemWindowInsets(WindowInsets in, Rect outLocalInsets) {
- if (mStable) {
+ if (mStable || !hasContentOnApplyWindowInsetsListener()) {
// Emulate the effect of makeOptionalFitsSystemWindows, because we can't do that
// without being attached to a window.
outLocalInsets.setEmpty();
@@ -202,6 +263,15 @@ public class ActionBarOverlayLayoutTest {
setSystemUiVisibility(stable ? SYSTEM_UI_FLAG_LAYOUT_STABLE : 0);
}
+ void setHasContentOnApplyWindowInsetsListener(boolean hasListener) {
+ mHasContentOnApplyWindowInsetsListener = hasListener;
+ }
+
+ @Override
+ protected boolean hasContentOnApplyWindowInsetsListener() {
+ return mHasContentOnApplyWindowInsetsListener;
+ }
+
@Override
public int getWindowSystemUiVisibility() {
return getSystemUiVisibility();
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index fc4277e60bc5..82d2381012e5 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -594,6 +594,8 @@ applications that come with the platform
<permission name="android.permission.EMERGENCY_INSTALL_PACKAGES" />
<!-- Permission required for Cts test - CtsSettingsTestCases -->
<permission name="android.permission.PREPARE_FACTORY_RESET" />
+ <!-- Permission required for CTS test - FileIntegrityManagerTest -->
+ <permission name="android.permission.SETUP_FSVERITY" />
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index a454d48ac863..e829d4ef650e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -17,8 +17,10 @@
package com.android.wm.shell.pip2.phone;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_PIP;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
@@ -182,6 +184,10 @@ public class PipTransition extends PipTransitionController {
mResizeTransition = null;
return startResizeAnimation(info, startTransaction, finishTransaction, finishCallback);
}
+
+ if (isRemovePipTransition(info)) {
+ return removePipImmediately(info, startTransaction, finishTransaction, finishCallback);
+ }
return false;
}
@@ -291,6 +297,10 @@ public class PipTransition extends PipTransitionController {
startOverlayFadeoutAnimation();
}
+ //
+ // Subroutines setting up and starting transitions' animations.
+ //
+
private void startOverlayFadeoutAnimation() {
ValueAnimator animator = ValueAnimator.ofFloat(1f, 0f);
animator.setDuration(CONTENT_OVERLAY_FADE_OUT_DELAY_MS);
@@ -326,6 +336,7 @@ public class PipTransition extends PipTransitionController {
mPipScheduler.setPipTaskToken(mPipTaskToken);
startTransaction.apply();
+ // TODO: b/275910498 Use a new implementation of the PiP animator here.
finishCallback.onTransitionFinished(null);
return true;
}
@@ -353,11 +364,26 @@ public class PipTransition extends PipTransitionController {
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
startTransaction.apply();
+ // TODO: b/275910498 Use a new implementation of the PiP animator here.
finishCallback.onTransitionFinished(null);
onExitPip();
return true;
}
+ private boolean removePipImmediately(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ startTransaction.apply();
+ finishCallback.onTransitionFinished(null);
+ onExitPip();
+ return true;
+ }
+
+ //
+ // Utility methods for checking PiP-related transition info and requests.
+ //
+
@Nullable
private TransitionInfo.Change getPipChange(TransitionInfo info) {
for (TransitionInfo.Change change : info.getChanges()) {
@@ -415,6 +441,25 @@ public class PipTransition extends PipTransitionController {
&& info.getChanges().size() == 1;
}
+ private boolean isRemovePipTransition(@NonNull TransitionInfo info) {
+ if (mPipTaskToken == null) {
+ // PiP removal makes sense if enter-PiP has cached a valid pinned task token.
+ return false;
+ }
+ TransitionInfo.Change pipChange = info.getChange(mPipTaskToken);
+ if (pipChange == null) {
+ // Search for the PiP change by token since the windowing mode might be FULLSCREEN now.
+ return false;
+ }
+
+ boolean isPipMovedToBack = info.getType() == TRANSIT_TO_BACK
+ && pipChange.getMode() == TRANSIT_TO_BACK;
+ boolean isPipClosed = info.getType() == TRANSIT_CLOSE
+ && pipChange.getMode() == TRANSIT_CLOSE;
+ // PiP is being removed if the pinned task is either moved to back or closed.
+ return isPipMovedToBack || isPipClosed;
+ }
+
/**
* TODO: b/275910498 Use a new implementation of the PiP animator here.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
index eebd13370321..77b8663861ab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
@@ -16,6 +16,9 @@
package com.android.wm.shell.recents;
+import android.annotation.Nullable;
+import android.graphics.Color;
+
import com.android.wm.shell.shared.annotations.ExternalThread;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
@@ -40,4 +43,12 @@ public interface RecentTasks {
*/
default void addAnimationStateListener(Executor listenerExecutor, Consumer<Boolean> listener) {
}
+
+ /**
+ * Sets a background color on the transition root layered behind the outgoing task. {@code null}
+ * may be used to clear any previously set colors to avoid showing a background at all. The
+ * color is always shown at full opacity.
+ */
+ default void setTransitionBackgroundColor(@Nullable Color color) {
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 0c99aed6852e..e7d9812e5393 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -30,6 +30,7 @@ import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.graphics.Color;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Slog;
@@ -476,6 +477,16 @@ public class RecentTasksController implements TaskStackListenerCallback,
});
});
}
+
+ @Override
+ public void setTransitionBackgroundColor(@Nullable Color color) {
+ mMainExecutor.execute(() -> {
+ if (mTransitionHandler == null) {
+ return;
+ }
+ mTransitionHandler.setTransitionBackgroundColor(color);
+ });
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 24cf3706e25a..c625b69deac0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -36,6 +36,7 @@ import android.app.ActivityTaskManager;
import android.app.IApplicationThread;
import android.app.PendingIntent;
import android.content.Intent;
+import android.graphics.Color;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
@@ -56,6 +57,8 @@ import android.window.TransitionRequestInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import androidx.annotation.NonNull;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IResultReceiver;
import com.android.internal.protolog.common.ProtoLog;
@@ -92,6 +95,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
private final ArrayList<RecentsMixedHandler> mMixers = new ArrayList<>();
private final HomeTransitionObserver mHomeTransitionObserver;
+ private @Nullable Color mBackgroundColor;
public RecentsTransitionHandler(ShellInit shellInit, Transitions transitions,
@Nullable RecentTasksController recentTasksController,
@@ -123,6 +127,15 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
mStateListeners.add(listener);
}
+ /**
+ * Sets a background color on the transition root layered behind the outgoing task. {@code null}
+ * may be used to clear any previously set colors to avoid showing a background at all. The
+ * color is always shown at full opacity.
+ */
+ public void setTransitionBackgroundColor(@Nullable Color color) {
+ mBackgroundColor = color;
+ }
+
@VisibleForTesting
public IBinder startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
IApplicationThread appThread, IRecentsAnimationRunner listener) {
@@ -469,6 +482,16 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
final int belowLayers = info.getChanges().size();
final int middleLayers = info.getChanges().size() * 2;
final int aboveLayers = info.getChanges().size() * 3;
+
+ // Add a background color to each transition root in this transition.
+ if (mBackgroundColor != null) {
+ info.getChanges().stream()
+ .mapToInt((change) -> TransitionUtil.rootIndexFor(change, info))
+ .distinct()
+ .mapToObj((rootIndex) -> info.getRoot(rootIndex).getLeash())
+ .forEach((root) -> createBackgroundSurface(t, root, middleLayers));
+ }
+
for (int i = 0; i < info.getChanges().size(); ++i) {
final TransitionInfo.Change change = info.getChanges().get(i);
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
@@ -1107,6 +1130,29 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
return true;
}
+ private void createBackgroundSurface(SurfaceControl.Transaction transaction,
+ SurfaceControl parent, int layer) {
+ if (mBackgroundColor == null) {
+ return;
+ }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ " adding background color to layer=%d", layer);
+ final SurfaceControl background = new SurfaceControl.Builder()
+ .setName("recents_background")
+ .setColorLayer()
+ .setOpaque(true)
+ .setParent(parent)
+ .build();
+ transaction.setColor(background, colorToFloatArray(mBackgroundColor));
+ transaction.setLayer(background, layer);
+ transaction.setAlpha(background, 1F);
+ transaction.show(background);
+ }
+
+ private static float[] colorToFloatArray(@NonNull Color color) {
+ return new float[]{color.red(), color.green(), color.blue()};
+ }
+
private void cleanUpPausingOrClosingTask(TaskState task, WindowContainerTransaction wct,
SurfaceControl.Transaction finishTransaction, boolean sendUserLeaveHint) {
if (!sendUserLeaveHint && task.isLeaf()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 2bbe530fbaf6..da1699cd6e33 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -467,8 +467,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
* until a resize event calls showResizeVeil below.
*/
void createResizeVeil() {
- mResizeVeil = new ResizeVeil(mContext, mAppIconDrawable, mTaskInfo, mTaskSurface,
- mSurfaceControlBuilderSupplier, mDisplay, mSurfaceControlTransactionSupplier);
+ mResizeVeil = new ResizeVeil(mContext, mDisplayController, mAppIconDrawable, mTaskInfo,
+ mTaskSurface, mSurfaceControlTransactionSupplier);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
index d072f8cec194..2c4092ac6d2c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
@@ -20,6 +20,7 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.ColorRes;
+import android.annotation.NonNull;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.content.res.Configuration;
@@ -40,7 +41,7 @@ import android.widget.ImageView;
import android.window.TaskConstants;
import com.android.wm.shell.R;
-import com.android.wm.shell.common.SurfaceUtils;
+import com.android.wm.shell.common.DisplayController;
import java.util.function.Supplier;
@@ -48,6 +49,7 @@ import java.util.function.Supplier;
* Creates and updates a veil that covers task contents on resize.
*/
public class ResizeVeil {
+ private static final String TAG = "ResizeVeil";
private static final int RESIZE_ALPHA_DURATION = 100;
private static final int VEIL_CONTAINER_LAYER = TaskConstants.TASK_CHILD_LAYER_RESIZE_VEIL;
@@ -57,8 +59,10 @@ public class ResizeVeil {
private static final int VEIL_ICON_LAYER = 1;
private final Context mContext;
- private final Supplier<SurfaceControl.Builder> mSurfaceControlBuilderSupplier;
+ private final DisplayController mDisplayController;
private final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
+ private final SurfaceControlBuilderFactory mSurfaceControlBuilderFactory;
+ private final WindowDecoration.SurfaceControlViewHostFactory mSurfaceControlViewHostFactory;
private final SurfaceSession mSurfaceSession = new SurfaceSession();
private final Drawable mAppIcon;
private ImageView mIconView;
@@ -74,41 +78,82 @@ public class ResizeVeil {
private final RunningTaskInfo mTaskInfo;
private SurfaceControlViewHost mViewHost;
- private final Display mDisplay;
+ private Display mDisplay;
private ValueAnimator mVeilAnimator;
- public ResizeVeil(Context context, Drawable appIcon, RunningTaskInfo taskInfo,
+ private boolean mIsShowing = false;
+
+ private final DisplayController.OnDisplaysChangedListener mOnDisplaysChangedListener =
+ new DisplayController.OnDisplaysChangedListener() {
+ @Override
+ public void onDisplayAdded(int displayId) {
+ if (mTaskInfo.displayId != displayId) {
+ return;
+ }
+ mDisplayController.removeDisplayWindowListener(this);
+ setupResizeVeil();
+ }
+ };
+
+ public ResizeVeil(Context context,
+ @NonNull DisplayController displayController,
+ Drawable appIcon, RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
- Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, Display display,
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier) {
+ this(context,
+ displayController,
+ appIcon,
+ taskInfo,
+ taskSurface,
+ surfaceControlTransactionSupplier,
+ new SurfaceControlBuilderFactory() {},
+ new WindowDecoration.SurfaceControlViewHostFactory() {});
+ }
+
+ public ResizeVeil(Context context,
+ @NonNull DisplayController displayController,
+ Drawable appIcon, RunningTaskInfo taskInfo,
+ SurfaceControl taskSurface,
+ Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
+ SurfaceControlBuilderFactory surfaceControlBuilderFactory,
+ WindowDecoration.SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
mContext = context;
+ mDisplayController = displayController;
mAppIcon = appIcon;
- mSurfaceControlBuilderSupplier = surfaceControlBuilderSupplier;
mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
mTaskInfo = taskInfo;
mParentSurface = taskSurface;
- mDisplay = display;
+ mSurfaceControlBuilderFactory = surfaceControlBuilderFactory;
+ mSurfaceControlViewHostFactory = surfaceControlViewHostFactory;
setupResizeVeil();
}
-
/**
* Create the veil in its default invisible state.
*/
private void setupResizeVeil() {
- mVeilSurface = mSurfaceControlBuilderSupplier.get()
+ if (!obtainDisplayOrRegisterListener()) {
+ // Display may not be available yet, skip this until then.
+ return;
+ }
+ mVeilSurface = mSurfaceControlBuilderFactory
+ .create("Resize veil of Task=" + mTaskInfo.taskId)
.setContainerLayer()
- .setName("Resize veil of Task=" + mTaskInfo.taskId)
.setHidden(true)
.setParent(mParentSurface)
.setCallsite("ResizeVeil#setupResizeVeil")
.build();
- mBackgroundSurface = SurfaceUtils.makeColorLayer(mVeilSurface,
- "Resize veil background of Task=" + mTaskInfo.taskId, mSurfaceSession);
- mIconSurface = mSurfaceControlBuilderSupplier.get()
- .setName("Resize veil icon of Task= " + mTaskInfo.taskId)
- .setContainerLayer()
+ mBackgroundSurface = mSurfaceControlBuilderFactory
+ .create("Resize veil background of Task=" + mTaskInfo.taskId, mSurfaceSession)
+ .setColorLayer()
+ .setHidden(true)
.setParent(mVeilSurface)
+ .setCallsite("ResizeVeil#setupResizeVeil")
+ .build();
+ mIconSurface = mSurfaceControlBuilderFactory
+ .create("Resize veil icon of Task=" + mTaskInfo.taskId)
+ .setContainerLayer()
.setHidden(true)
+ .setParent(mVeilSurface)
.setCallsite("ResizeVeil#setupResizeVeil")
.build();
@@ -131,10 +176,20 @@ public class ResizeVeil {
final WindowlessWindowManager wwm = new WindowlessWindowManager(mTaskInfo.configuration,
mIconSurface, null /* hostInputToken */);
- mViewHost = new SurfaceControlViewHost(mContext, mDisplay, wwm, "ResizeVeil");
+
+ mViewHost = mSurfaceControlViewHostFactory.create(mContext, mDisplay, wwm, "ResizeVeil");
mViewHost.setView(root, lp);
}
+ private boolean obtainDisplayOrRegisterListener() {
+ mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
+ if (mDisplay == null) {
+ mDisplayController.addDisplayWindowListener(mOnDisplaysChangedListener);
+ return false;
+ }
+ return true;
+ }
+
/**
* Shows the veil surface/view.
*
@@ -146,6 +201,12 @@ public class ResizeVeil {
*/
public void showVeil(SurfaceControl.Transaction t, SurfaceControl parentSurface,
Rect taskBounds, boolean fadeIn) {
+ if (!isReady() || isVisible()) {
+ t.apply();
+ return;
+ }
+ mIsShowing = true;
+
// Parent surface can change, ensure it is up to date.
if (!parentSurface.equals(mParentSurface)) {
t.reparent(mVeilSurface, parentSurface);
@@ -226,6 +287,9 @@ public class ResizeVeil {
* Animate veil's alpha to 1, fading it in.
*/
public void showVeil(SurfaceControl parentSurface, Rect taskBounds) {
+ if (!isReady() || isVisible()) {
+ return;
+ }
SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
showVeil(t, parentSurface, taskBounds, true /* fadeIn */);
}
@@ -247,6 +311,9 @@ public class ResizeVeil {
* @param newBounds bounds to update veil to.
*/
public void updateResizeVeil(Rect newBounds) {
+ if (!isVisible()) {
+ return;
+ }
SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
updateResizeVeil(t, newBounds);
}
@@ -260,6 +327,10 @@ public class ResizeVeil {
* @param newBounds bounds to update veil to.
*/
public void updateResizeVeil(SurfaceControl.Transaction t, Rect newBounds) {
+ if (!isVisible()) {
+ t.apply();
+ return;
+ }
if (mVeilAnimator != null && mVeilAnimator.isStarted()) {
mVeilAnimator.removeAllUpdateListeners();
mVeilAnimator.end();
@@ -272,6 +343,9 @@ public class ResizeVeil {
* Animate veil's alpha to 0, fading it out.
*/
public void hideVeil() {
+ if (!isVisible()) {
+ return;
+ }
cancelAnimation();
mVeilAnimator = new ValueAnimator();
mVeilAnimator.setFloatValues(1, 0);
@@ -292,6 +366,7 @@ public class ResizeVeil {
}
});
mVeilAnimator.start();
+ mIsShowing = false;
}
@ColorRes
@@ -318,10 +393,26 @@ public class ResizeVeil {
}
/**
+ * Whether the resize veil is currently visible.
+ *
+ * Note: when animating a {@link ResizeVeil#hideVeil()}, the veil is considered visible as soon
+ * as the animation starts.
+ */
+ private boolean isVisible() {
+ return mIsShowing;
+ }
+
+ /** Whether the resize veil is ready to be shown. */
+ private boolean isReady() {
+ return mViewHost != null;
+ }
+
+ /**
* Dispose of veil when it is no longer needed, likely on close of its container decor.
*/
void dispose() {
cancelAnimation();
+ mIsShowing = false;
mVeilAnimator = null;
if (mViewHost != null) {
@@ -342,5 +433,16 @@ public class ResizeVeil {
mVeilSurface = null;
}
t.apply();
+ mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener);
+ }
+
+ interface SurfaceControlBuilderFactory {
+ default SurfaceControl.Builder create(@NonNull String name) {
+ return new SurfaceControl.Builder().setName(name);
+ }
+ default SurfaceControl.Builder create(@NonNull String name,
+ @NonNull SurfaceSession surfaceSession) {
+ return new SurfaceControl.Builder(surfaceSession).setName(name);
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 51b0a246f3e9..36da1ace8408 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -668,6 +668,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) {
return new SurfaceControlViewHost(c, d, wmm, "WindowDecoration");
}
+ default SurfaceControlViewHost create(Context c, Display d,
+ WindowlessWindowManager wmm, String callsite) {
+ return new SurfaceControlViewHost(c, d, wmm, callsite);
+ }
}
/**
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt
new file mode 100644
index 000000000000..847c2dd77d0a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt
@@ -0,0 +1,216 @@
+/*
+ * 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.wm.shell.windowdecor
+
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.Display
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import android.view.WindowlessWindowManager
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
+import com.android.wm.shell.windowdecor.WindowDecoration.SurfaceControlViewHostFactory
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Spy
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.whenever
+
+
+/**
+ * Tests for [ResizeVeil].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:ResizeVeilTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class ResizeVeilTest : ShellTestCase() {
+
+ @Mock
+ private lateinit var mockDisplayController: DisplayController
+ @Mock
+ private lateinit var mockAppIcon: Drawable
+ @Mock
+ private lateinit var mockDisplay: Display
+ @Mock
+ private lateinit var mockSurfaceControlViewHost: SurfaceControlViewHost
+ @Mock
+ private lateinit var mockSurfaceControlBuilderFactory: ResizeVeil.SurfaceControlBuilderFactory
+ @Mock
+ private lateinit var mockSurfaceControlViewHostFactory: SurfaceControlViewHostFactory
+ @Spy
+ private val spyResizeVeilSurfaceBuilder = SurfaceControl.Builder()
+ @Mock
+ private lateinit var mockResizeVeilSurface: SurfaceControl
+ @Spy
+ private val spyBackgroundSurfaceBuilder = SurfaceControl.Builder()
+ @Mock
+ private lateinit var mockBackgroundSurface: SurfaceControl
+ @Spy
+ private val spyIconSurfaceBuilder = SurfaceControl.Builder()
+ @Mock
+ private lateinit var mockIconSurface: SurfaceControl
+ @Mock
+ private lateinit var mockTransaction: SurfaceControl.Transaction
+
+ private val taskInfo = TestRunningTaskInfoBuilder().build()
+
+ @Before
+ fun setUp() {
+ whenever(mockSurfaceControlViewHostFactory.create(any(), any(), any(), any()))
+ .thenReturn(mockSurfaceControlViewHost)
+ whenever(mockSurfaceControlBuilderFactory
+ .create("Resize veil of Task=" + taskInfo.taskId))
+ .thenReturn(spyResizeVeilSurfaceBuilder)
+ doReturn(mockResizeVeilSurface).whenever(spyResizeVeilSurfaceBuilder).build()
+ whenever(mockSurfaceControlBuilderFactory
+ .create(eq("Resize veil background of Task=" + taskInfo.taskId), any()))
+ .thenReturn(spyBackgroundSurfaceBuilder)
+ doReturn(mockBackgroundSurface).whenever(spyBackgroundSurfaceBuilder).build()
+ whenever(mockSurfaceControlBuilderFactory
+ .create("Resize veil icon of Task=" + taskInfo.taskId))
+ .thenReturn(spyIconSurfaceBuilder)
+ doReturn(mockIconSurface).whenever(spyIconSurfaceBuilder).build()
+ }
+
+ @Test
+ fun init_displayAvailable_viewHostCreated() {
+ createResizeVeil(withDisplayAvailable = true)
+
+ verify(mockSurfaceControlViewHostFactory)
+ .create(any(), eq(mockDisplay), any(), eq("ResizeVeil"))
+ }
+
+ @Test
+ fun init_displayUnavailable_viewHostNotCreatedUntilDisplayAppears() {
+ createResizeVeil(withDisplayAvailable = false)
+
+ verify(mockSurfaceControlViewHostFactory, never())
+ .create(any(), eq(mockDisplay), any<WindowlessWindowManager>(), eq("ResizeVeil"))
+ val captor = ArgumentCaptor.forClass(OnDisplaysChangedListener::class.java)
+ verify(mockDisplayController).addDisplayWindowListener(captor.capture())
+
+ whenever(mockDisplayController.getDisplay(taskInfo.displayId)).thenReturn(mockDisplay)
+ captor.value.onDisplayAdded(taskInfo.displayId)
+
+ verify(mockSurfaceControlViewHostFactory)
+ .create(any(), eq(mockDisplay), any(), eq("ResizeVeil"))
+ verify(mockDisplayController).removeDisplayWindowListener(any())
+ }
+
+ @Test
+ fun dispose_removesDisplayWindowListener() {
+ createResizeVeil().dispose()
+
+ verify(mockDisplayController).removeDisplayWindowListener(any())
+ }
+
+ @Test
+ fun showVeil() {
+ val veil = createResizeVeil()
+ val tx = mock<SurfaceControl.Transaction>()
+
+ veil.showVeil(tx, mock(), Rect(0, 0, 100, 100), false /* fadeIn */)
+
+ verify(tx).show(mockResizeVeilSurface)
+ verify(tx).show(mockBackgroundSurface)
+ verify(tx).show(mockIconSurface)
+ verify(tx).apply()
+ }
+
+ @Test
+ fun showVeil_displayUnavailable_doesNotShow() {
+ val veil = createResizeVeil(withDisplayAvailable = false)
+ val tx = mock<SurfaceControl.Transaction>()
+
+ veil.showVeil(tx, mock(), Rect(0, 0, 100, 100), false /* fadeIn */)
+
+ verify(tx, never()).show(mockResizeVeilSurface)
+ verify(tx, never()).show(mockBackgroundSurface)
+ verify(tx, never()).show(mockIconSurface)
+ verify(tx).apply()
+ }
+
+ @Test
+ fun showVeil_alreadyVisible_doesNotShowAgain() {
+ val veil = createResizeVeil()
+ val tx = mock<SurfaceControl.Transaction>()
+
+ veil.showVeil(tx, mock(), Rect(0, 0, 100, 100), false /* fadeIn */)
+ veil.showVeil(tx, mock(), Rect(0, 0, 100, 100), false /* fadeIn */)
+
+ verify(tx, times(1)).show(mockResizeVeilSurface)
+ verify(tx, times(1)).show(mockBackgroundSurface)
+ verify(tx, times(1)).show(mockIconSurface)
+ verify(tx, times(2)).apply()
+ }
+
+ @Test
+ fun showVeil_reparentsVeilToNewParent() {
+ val veil = createResizeVeil(parent = mock())
+ val tx = mock<SurfaceControl.Transaction>()
+
+ val newParent = mock<SurfaceControl>()
+ veil.showVeil(tx, newParent, Rect(0, 0, 100, 100), false /* fadeIn */)
+
+ verify(tx).reparent(mockResizeVeilSurface, newParent)
+ }
+
+ @Test
+ fun hideVeil_alreadyHidden_doesNothing() {
+ val veil = createResizeVeil()
+
+ veil.hideVeil()
+
+ verifyZeroInteractions(mockTransaction)
+ }
+
+ private fun createResizeVeil(
+ withDisplayAvailable: Boolean = true,
+ parent: SurfaceControl = mock()
+ ): ResizeVeil {
+ whenever(mockDisplayController.getDisplay(taskInfo.displayId))
+ .thenReturn(if (withDisplayAvailable) mockDisplay else null)
+ return ResizeVeil(
+ context,
+ mockDisplayController,
+ mockAppIcon,
+ taskInfo,
+ parent,
+ { mockTransaction },
+ mockSurfaceControlBuilderFactory,
+ mockSurfaceControlViewHostFactory
+ )
+ }
+}
diff --git a/libs/hostgraphics/gui/Surface.h b/libs/hostgraphics/gui/Surface.h
index 2573931c8543..36d8fba0d61a 100644
--- a/libs/hostgraphics/gui/Surface.h
+++ b/libs/hostgraphics/gui/Surface.h
@@ -52,6 +52,8 @@ public:
virtual void destroy() {}
+ int getBuffersDataSpace() { return 0; }
+
protected:
virtual ~Surface() {}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 7439fbc1149c..753a69960b4c 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -1,4 +1,5 @@
package {
+ default_team: "trendy_team_android_core_graphics_stack",
default_applicable_licenses: ["frameworks_base_libs_hwui_license"],
}
@@ -93,6 +94,7 @@ cc_defaults {
host: {
include_dirs: [
"external/vulkan-headers/include",
+ "frameworks/av/media/ndk/include",
],
cflags: [
"-Wno-unused-variable",
@@ -142,7 +144,6 @@ cc_defaults {
"libsync",
"libui",
"aconfig_text_flags_c_lib",
- "server_configurable_flags",
],
static_libs: [
"libEGL_blobCache",
@@ -267,6 +268,7 @@ cc_defaults {
cppflags: ["-Wno-conversion-null"],
srcs: [
+ "apex/android_canvas.cpp",
"apex/android_matrix.cpp",
"apex/android_paint.cpp",
"apex/android_region.cpp",
@@ -279,7 +281,6 @@ cc_defaults {
android: {
srcs: [ // sources that depend on android only libraries
"apex/android_bitmap.cpp",
- "apex/android_canvas.cpp",
"apex/jni_runtime.cpp",
],
},
@@ -338,6 +339,8 @@ cc_defaults {
"jni/android_graphics_ColorSpace.cpp",
"jni/android_graphics_drawable_AnimatedVectorDrawable.cpp",
"jni/android_graphics_drawable_VectorDrawable.cpp",
+ "jni/android_graphics_HardwareRenderer.cpp",
+ "jni/android_graphics_HardwareBufferRenderer.cpp",
"jni/android_graphics_HardwareRendererObserver.cpp",
"jni/android_graphics_Matrix.cpp",
"jni/android_graphics_Picture.cpp",
@@ -422,8 +425,6 @@ cc_defaults {
android: {
srcs: [ // sources that depend on android only libraries
"jni/android_graphics_TextureLayer.cpp",
- "jni/android_graphics_HardwareRenderer.cpp",
- "jni/android_graphics_HardwareBufferRenderer.cpp",
"jni/GIFMovie.cpp",
"jni/GraphicsStatsService.cpp",
"jni/Movie.cpp",
@@ -448,6 +449,12 @@ cc_defaults {
"libstatssocket_lazy",
],
},
+ linux: {
+ srcs: ["platform/linux/utils/SharedLib.cpp"],
+ },
+ darwin: {
+ srcs: ["platform/darwin/utils/SharedLib.cpp"],
+ },
host: {
cflags: [
"-Wno-unused-const-variable",
@@ -543,6 +550,7 @@ cc_defaults {
"renderthread/CanvasContext.cpp",
"renderthread/DrawFrameTask.cpp",
"renderthread/Frame.cpp",
+ "renderthread/RenderEffectCapabilityQuery.cpp",
"renderthread/RenderProxy.cpp",
"renderthread/RenderTask.cpp",
"renderthread/TimeLord.cpp",
@@ -576,6 +584,7 @@ cc_defaults {
"HWUIProperties.sysprop",
"Interpolator.cpp",
"JankTracker.cpp",
+ "Layer.cpp",
"LayerUpdateQueue.cpp",
"LightingInfo.cpp",
"Matrix.cpp",
@@ -624,7 +633,6 @@ cc_defaults {
"renderthread/CacheManager.cpp",
"renderthread/EglManager.cpp",
"renderthread/ReliableSurface.cpp",
- "renderthread/RenderEffectCapabilityQuery.cpp",
"renderthread/VulkanManager.cpp",
"renderthread/VulkanSurface.cpp",
"renderthread/RenderThread.cpp",
@@ -635,7 +643,6 @@ cc_defaults {
"AutoBackendTextureRelease.cpp",
"DeferredLayerUpdater.cpp",
"HardwareBitmapUploader.cpp",
- "Layer.cpp",
"ProfileDataContainer.cpp",
"Readback.cpp",
"WebViewFunctorManager.cpp",
diff --git a/libs/hwui/jni/HardwareBufferHelpers.cpp b/libs/hwui/jni/HardwareBufferHelpers.cpp
index 7e3f771b6b3d..d3b48d36b677 100644
--- a/libs/hwui/jni/HardwareBufferHelpers.cpp
+++ b/libs/hwui/jni/HardwareBufferHelpers.cpp
@@ -16,7 +16,9 @@
#include "HardwareBufferHelpers.h"
+#ifdef __ANDROID__
#include <dlfcn.h>
+#endif
#include <log/log.h>
#ifdef __ANDROID__
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index d9e2c8c25327..df9f83036709 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -25,13 +25,16 @@
#include <SkColorSpace.h>
#include <SkData.h>
#include <SkImage.h>
+#ifdef __ANDROID__
#include <SkImageAndroid.h>
+#else
+#include <SkImagePriv.h>
+#endif
#include <SkPicture.h>
#include <SkPixmap.h>
#include <SkSerialProcs.h>
#include <SkStream.h>
#include <SkTypeface.h>
-#include <dlfcn.h>
#include <gui/TraceUtils.h>
#include <include/encode/SkPngEncoder.h>
#include <inttypes.h>
@@ -39,8 +42,10 @@
#include <media/NdkImage.h>
#include <media/NdkImageReader.h>
#include <nativehelper/JNIPlatformHelp.h>
+#ifdef __ANDROID__
#include <pipeline/skia/ShaderCache.h>
#include <private/EGL/cache.h>
+#endif
#include <renderthread/CanvasContext.h>
#include <renderthread/RenderProxy.h>
#include <renderthread/RenderTask.h>
@@ -59,6 +64,7 @@
#include "JvmErrorReporter.h"
#include "android_graphics_HardwareRendererObserver.h"
#include "utils/ForceDark.h"
+#include "utils/SharedLib.h"
namespace android {
@@ -498,7 +504,11 @@ public:
return sk_ref_sp(img);
}
bm.setImmutable();
+#ifdef __ANDROID__
return SkImages::PinnableRasterFromBitmap(bm);
+#else
+ return SkMakeImageFromRasterBitmap(bm, kNever_SkCopyPixelsMode);
+#endif
}
return sk_ref_sp(img);
}
@@ -713,6 +723,7 @@ public:
static jobject android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode(JNIEnv* env,
jobject clazz, jlong renderNodePtr, jint jwidth, jint jheight) {
+#ifdef __ANDROID__
RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
if (jwidth <= 0 || jheight <= 0) {
ALOGW("Invalid width %d or height %d", jwidth, jheight);
@@ -796,6 +807,9 @@ static jobject android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode(
sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer, cs);
return bitmap::createBitmap(env, bitmap.release(),
android::bitmap::kBitmapCreateFlag_Premultiplied);
+#else
+ return nullptr;
+#endif
}
static void android_view_ThreadedRenderer_disableVsync(JNIEnv*, jclass) {
@@ -909,6 +923,7 @@ static void android_view_ThreadedRenderer_removeObserver(JNIEnv* env, jclass cla
static void android_view_ThreadedRenderer_setupShadersDiskCache(JNIEnv* env, jobject clazz,
jstring diskCachePath, jstring skiaDiskCachePath) {
+#ifdef __ANDROID__
const char* cacheArray = env->GetStringUTFChars(diskCachePath, NULL);
android::egl_set_cache_filename(cacheArray);
env->ReleaseStringUTFChars(diskCachePath, cacheArray);
@@ -916,6 +931,7 @@ static void android_view_ThreadedRenderer_setupShadersDiskCache(JNIEnv* env, job
const char* skiaCacheArray = env->GetStringUTFChars(skiaDiskCachePath, NULL);
uirenderer::skiapipeline::ShaderCache::get().setFilename(skiaCacheArray);
env->ReleaseStringUTFChars(skiaDiskCachePath, skiaCacheArray);
+#endif
}
static jboolean android_view_ThreadedRenderer_isWebViewOverlaysEnabled(JNIEnv* env, jobject clazz) {
@@ -1092,8 +1108,12 @@ int register_android_view_ThreadedRenderer(JNIEnv* env) {
gCopyRequest.getDestinationBitmap =
GetMethodIDOrDie(env, copyRequest, "getDestinationBitmap", "(II)J");
- void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
- fromSurface = (ANW_fromSurface)dlsym(handle_, "ANativeWindow_fromSurface");
+#ifdef __ANDROID__
+ void* handle_ = SharedLib::openSharedLib("libandroid");
+#else
+ void* handle_ = SharedLib::openSharedLib("libandroid_runtime");
+#endif
+ fromSurface = (ANW_fromSurface)SharedLib::getSymbol(handle_, "ANativeWindow_fromSurface");
LOG_ALWAYS_FATAL_IF(fromSurface == nullptr,
"Failed to find required symbol ANativeWindow_fromSurface!");
diff --git a/libs/hwui/platform/darwin/utils/SharedLib.cpp b/libs/hwui/platform/darwin/utils/SharedLib.cpp
new file mode 100644
index 000000000000..6e9f0b486513
--- /dev/null
+++ b/libs/hwui/platform/darwin/utils/SharedLib.cpp
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+#include "utils/SharedLib.h"
+
+#include <dlfcn.h>
+
+namespace android {
+namespace uirenderer {
+
+void* SharedLib::openSharedLib(std::string filename) {
+ return dlopen((filename + ".dylib").c_str(), RTLD_NOW | RTLD_NODELETE);
+}
+
+void* SharedLib::getSymbol(void* library, const char* symbol) {
+ return dlsym(library, symbol);
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/platform/linux/utils/SharedLib.cpp b/libs/hwui/platform/linux/utils/SharedLib.cpp
new file mode 100644
index 000000000000..a9acf37dfef4
--- /dev/null
+++ b/libs/hwui/platform/linux/utils/SharedLib.cpp
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+#include "utils/SharedLib.h"
+
+#include <dlfcn.h>
+
+namespace android {
+namespace uirenderer {
+
+void* SharedLib::openSharedLib(std::string filename) {
+ return dlopen((filename + ".so").c_str(), RTLD_NOW | RTLD_NODELETE);
+}
+
+void* SharedLib::getSymbol(void* library, const char* symbol) {
+ return dlsym(library, symbol);
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/utils/SharedLib.h b/libs/hwui/utils/SharedLib.h
new file mode 100644
index 000000000000..f4dcf0f664a2
--- /dev/null
+++ b/libs/hwui/utils/SharedLib.h
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+#ifndef SHAREDLIB_H
+#define SHAREDLIB_H
+
+#include <string>
+
+namespace android {
+namespace uirenderer {
+
+class SharedLib {
+public:
+ static void* openSharedLib(std::string filename);
+ static void* getSymbol(void* library, const char* symbol);
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif // SHAREDLIB_H
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index 19e59a776511..d6d4989aa47e 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -79,6 +79,13 @@ flag {
}
flag {
+ name: "subscriptions_listener_thread"
+ namespace: "location"
+ description: "Flag for running onSubscriptionsChangeListener on FgThread"
+ bug: "332451908"
+}
+
+flag {
name: "gnss_configuration_from_resource"
namespace: "location"
description: "Flag for GNSS configuration from resource"
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index ce7474c024a3..3ba0d599fde8 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -5232,6 +5232,13 @@ final public class MediaCodec {
setParameters(keys, values);
}
+ private void logAndRun(String message, Runnable r) {
+ final String TAG = "MediaCodec";
+ android.util.Log.d(TAG, "enter: " + message);
+ r.run();
+ android.util.Log.d(TAG, "exit : " + message);
+ }
+
/**
* Sets an asynchronous callback for actionable MediaCodec events.
*
@@ -5261,14 +5268,40 @@ final public class MediaCodec {
// even if we were to extend this to be callable dynamically, it must
// be called when codec is flushed, so no messages are pending.
if (newHandler != mCallbackHandler) {
- mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
- mCallbackHandler.removeMessages(EVENT_CALLBACK);
+ if (android.media.codec.Flags.setCallbackStall()) {
+ logAndRun(
+ "[new handler] removeMessages(SET_CALLBACK)",
+ () -> {
+ mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
+ });
+ logAndRun(
+ "[new handler] removeMessages(CALLBACK)",
+ () -> {
+ mCallbackHandler.removeMessages(EVENT_CALLBACK);
+ });
+ } else {
+ mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
+ mCallbackHandler.removeMessages(EVENT_CALLBACK);
+ }
mCallbackHandler = newHandler;
}
}
} else if (mCallbackHandler != null) {
- mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
- mCallbackHandler.removeMessages(EVENT_CALLBACK);
+ if (android.media.codec.Flags.setCallbackStall()) {
+ logAndRun(
+ "[null handler] removeMessages(SET_CALLBACK)",
+ () -> {
+ mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
+ });
+ logAndRun(
+ "[null handler] removeMessages(CALLBACK)",
+ () -> {
+ mCallbackHandler.removeMessages(EVENT_CALLBACK);
+ });
+ } else {
+ mCallbackHandler.removeMessages(EVENT_SET_CALLBACK);
+ mCallbackHandler.removeMessages(EVENT_CALLBACK);
+ }
}
if (mCallbackHandler != null) {
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 5331046dd0e3..6593533a843a 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -205,6 +205,10 @@ public final class MediaDrm implements AutoCloseable {
* media container format specified by mimeType at the requested
* security level.
*
+ * Calling this method while the application is running on the physical Android device or a
+ * {@link android.companion.virtual.VirtualDevice} may lead to different results, based on
+ * the different DRM capabilities of the devices.
+ *
* @param uuid The UUID of the crypto scheme.
* @param mimeType The MIME type of the media container, e.g. "video/mp4"
* or "video/webm"
@@ -1400,6 +1404,10 @@ public final class MediaDrm implements AutoCloseable {
* Open a new session with the MediaDrm object. A session ID is returned.
* By default, sessions are opened at the native security level of the device.
*
+ * If the application is currently running on a {@link android.companion.virtual.VirtualDevice}
+ * the security level will be adjusted accordingly to the maximum supported level for the
+ * display.
+ *
* @throws NotProvisionedException if provisioning is needed
* @throws ResourceBusyException if required resources are in use
*/
@@ -1422,6 +1430,10 @@ public final class MediaDrm implements AutoCloseable {
* can be queried using {@link #getSecurityLevel}. A session
* ID is returned.
*
+ * If the application is currently running on a {@link android.companion.virtual.VirtualDevice}
+ * the security level will be adjusted accordingly to the maximum supported level for the
+ * display.
+ *
* @param level the new security level
* @throws NotProvisionedException if provisioning is needed
* @throws ResourceBusyException if required resources are in use
@@ -2180,6 +2192,11 @@ public final class MediaDrm implements AutoCloseable {
* Returns a value that may be passed as a parameter to {@link #openSession(int)}
* requesting that the session be opened at the maximum security level of
* the device.
+ *
+ * This security level is only valid for the application running on the physical Android
+ * device (e.g. {@link android.content.Context#DEVICE_ID_DEFAULT}). While running on a
+ * {@link android.companion.virtual.VirtualDevice} the maximum supported security level
+ * might be different.
*/
public static final int getMaxSecurityLevel() {
return SECURITY_LEVEL_MAX;
diff --git a/media/java/android/media/midi/package.html b/media/java/android/media/midi/package.html
index 45b4370d6cc9..28d545d1fb15 100644
--- a/media/java/android/media/midi/package.html
+++ b/media/java/android/media/midi/package.html
@@ -332,7 +332,8 @@ AndroidManifest.xml file.</p>
<pre class=prettyprint>
&lt;service android:name="<strong>MySynthDeviceService</strong>"
- android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE">
+ android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE"
+ android:exported="true">
&lt;intent-filter>
&lt;action android:name="android.media.midi.MidiDeviceService" />
&lt;/intent-filter>
@@ -474,7 +475,8 @@ MIDI 1.0 virtual devices, android.media.midi.MidiUmpDeviceService is used</p>
<pre class=prettyprint>
&lt;service android:name="<strong>MidiEchoDeviceService</strong>"
- android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE">
+ android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE"
+ android:exported="true">
&lt;intent-filter>
&lt;action android:name="android.media.midi.MidiUmpDeviceService" />
&lt;/intent-filter>
@@ -528,8 +530,8 @@ public class MidiEchoDeviceService extends MidiUmpDeviceService {
&#64;Override
// Declare the receivers associated with your input ports.
- public List<MidiReceiver> onGetInputPortReceivers() {
- return new ArrayList<MidiReceiver>(Collections.singletonList(mInputReceiver));
+ public List&lt;MidiReceiver> onGetInputPortReceivers() {
+ return new ArrayList&lt;MidiReceiver>(Collections.singletonList(mInputReceiver));
}
/**
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 47637b82111a..4bacbf9a946e 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -15,10 +15,7 @@
*/
package android.media.session;
-import static com.android.media.flags.Flags.FLAG_ENABLE_NOTIFYING_ACTIVITY_MANAGER_WITH_MEDIA_SESSION_STATUS_CHANGE;
-
import android.annotation.DrawableRes;
-import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.LongDef;
import android.annotation.Nullable;
@@ -298,8 +295,9 @@ public final class PlaybackState implements Parcelable {
* foreground.
*
* @see Builder#setState
+ * @hide
*/
- @FlaggedApi(FLAG_ENABLE_NOTIFYING_ACTIVITY_MANAGER_WITH_MEDIA_SESSION_STATUS_CHANGE)
+ // TODO: b/335561702 Unhide this symbol for the next API bump.
public static final int STATE_PLAYBACK_SUPPRESSED = 12;
/**
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 94fce797f5d6..8609c4df1298 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -82,6 +82,7 @@ cc_library_shared {
"libhidlbase",
"libsonivox",
"server_configurable_flags",
+ "android.companion.virtual.virtualdevice_aidl-cpp",
"android.hardware.cas@1.0",
"android.hardware.cas.native@1.0",
"android.hardware.drm@1.3",
@@ -100,6 +101,7 @@ cc_library_shared {
static_libs: [
"libgrallocusage",
"libmedia_midiiowrapper",
+ "android.companion.virtualdevice.flags-aconfig-cc",
"android.media.playback.flags-aconfig-cc",
],
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index 1c25080939da..48cd53dc44d7 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -27,6 +27,8 @@
#include "jni.h"
#include <nativehelper/JNIHelp.h>
+#include <android_companion_virtualdevice_flags.h>
+#include <android/companion/virtualnative/IVirtualDeviceManagerNative.h>
#include <android/hardware/drm/1.3/IDrmFactory.h>
#include <binder/Parcel.h>
#include <binder/PersistableBundle.h>
@@ -41,8 +43,10 @@
#include <map>
#include <string>
+using ::android::companion::virtualnative::IVirtualDeviceManagerNative;
using ::android::os::PersistableBundle;
namespace drm = ::android::hardware::drm;
+namespace virtualdevice_flags = android::companion::virtualdevice::flags;
namespace android {
@@ -1045,6 +1049,26 @@ DrmPlugin::SecurityLevel jintToSecurityLevel(jint jlevel) {
return level;
}
+std::vector<int> getVirtualDeviceIds() {
+ if (!virtualdevice_flags::device_aware_drm()) {
+ ALOGW("Device-aware DRM flag disabled.");
+ return std::vector<int>();
+ }
+
+ sp<IBinder> binder =
+ defaultServiceManager()->checkService(String16("virtualdevice_native"));
+ if (binder != nullptr) {
+ auto vdm = interface_cast<IVirtualDeviceManagerNative>(binder);
+ std::vector<int> deviceIds;
+ const uid_t uid = IPCThreadState::self()->getCallingUid();
+ vdm->getDeviceIdsForUid(uid, &deviceIds);
+ return deviceIds;
+ } else {
+ ALOGW("Cannot get virtualdevice_native service");
+ return std::vector<int>();
+ }
+}
+
static jbyteArray android_media_MediaDrm_getSupportedCryptoSchemesNative(JNIEnv *env) {
sp<IDrm> drm = android::DrmUtils::MakeDrm();
if (drm == NULL) return env->NewByteArray(0);
@@ -1081,6 +1105,15 @@ static jboolean android_media_MediaDrm_isCryptoSchemeSupportedNative(
}
DrmPlugin::SecurityLevel securityLevel = jintToSecurityLevel(jSecurityLevel);
+ if (getVirtualDeviceIds().size() > 0) {
+ // Cap security level at max SECURITY_LEVEL_SW_SECURE_CRYPTO because at
+ // higher security levels decode output cannot be captured and
+ // streamed to virtual devices rendered on virtual displays.
+ if (securityLevel > DrmPlugin::kSecurityLevelSwSecureCrypto) {
+ return false;
+ }
+ }
+
bool isSupported;
status_t err = JDrm::IsCryptoSchemeSupported(uuid.array(), mimeType,
securityLevel, &isSupported);
@@ -1106,6 +1139,16 @@ static jbyteArray android_media_MediaDrm_openSession(
return NULL;
}
+ if (getVirtualDeviceIds().size() > 0) {
+ // Cap security level at max SECURITY_LEVEL_SW_SECURE_CRYPTO because at
+ // higher security levels decode output cannot be captured and
+ // streamed to virtual devices rendered on virtual displays.
+ if (level == DrmPlugin::kSecurityLevelMax ||
+ level > DrmPlugin::kSecurityLevelSwSecureCrypto) {
+ level = DrmPlugin::kSecurityLevelSwSecureCrypto;
+ }
+ }
+
DrmStatus err = drm->openSession(level, sessionId);
if (throwExceptionAsNecessary(env, drm, err, "Failed to open session")) {
diff --git a/media/jni/android_media_MediaDrm.h b/media/jni/android_media_MediaDrm.h
index a64e3f2a7cf0..36cba2dc3c7e 100644
--- a/media/jni/android_media_MediaDrm.h
+++ b/media/jni/android_media_MediaDrm.h
@@ -19,6 +19,8 @@
#include "jni.h"
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
#include <media/stagefright/foundation/ABase.h>
#include <mediadrm/IDrm.h>
#include <mediadrm/IDrmClient.h>
diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
index b57d54821b75..7cd7e7ab49a9 100644
--- a/nfc/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -35,6 +35,7 @@ import android.nfc.INfcDta;
import android.nfc.INfcWlcStateListener;
import android.nfc.NfcAntennaInfo;
import android.nfc.WlcListenerDeviceInfo;
+import android.nfc.cardemulation.PollingFrame;
import android.os.Bundle;
/**
@@ -101,7 +102,7 @@ interface INfcAdapter
void updateDiscoveryTechnology(IBinder b, int pollFlags, int listenFlags);
- void notifyPollingLoop(in Bundle frame);
+ void notifyPollingLoop(in PollingFrame frame);
void notifyHceDeactivated();
int sendVendorNciMessage(int mt, int gid, int oid, in byte[] payload);
void registerVendorExtensionCallback(in INfcVendorNciCallback callbacks);
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index b44a71bf9be0..29867d924c78 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -2803,12 +2803,11 @@ public final class NfcAdapter {
@TestApi
@FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
public void notifyPollingLoop(@NonNull PollingFrame pollingFrame) {
- Bundle frame = pollingFrame.toBundle();
try {
if (sService == null) {
attemptDeadServiceRecovery(null);
}
- sService.notifyPollingLoop(frame);
+ sService.notifyPollingLoop(pollingFrame);
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
// Try one more time
@@ -2817,7 +2816,7 @@ public final class NfcAdapter {
return;
}
try {
- sService.notifyPollingLoop(frame);
+ sService.notifyPollingLoop(pollingFrame);
} catch (RemoteException ee) {
Log.e(TAG, "Failed to recover NFC Service.");
}
diff --git a/nfc/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java
index 61037a2e7e9e..f674b06ad33d 100644
--- a/nfc/java/android/nfc/cardemulation/HostApduService.java
+++ b/nfc/java/android/nfc/cardemulation/HostApduService.java
@@ -325,15 +325,12 @@ public abstract class HostApduService extends Service {
}
break;
case MSG_POLLING_LOOP:
- ArrayList<Bundle> frames =
- msg.getData().getParcelableArrayList(KEY_POLLING_LOOP_FRAMES_BUNDLE,
- Bundle.class);
- ArrayList<PollingFrame> pollingFrames =
- new ArrayList<PollingFrame>(frames.size());
- for (Bundle frame : frames) {
- pollingFrames.add(new PollingFrame(frame));
+ if (android.nfc.Flags.nfcReadPollingLoop()) {
+ ArrayList<PollingFrame> pollingFrames =
+ msg.getData().getParcelableArrayList(
+ KEY_POLLING_LOOP_FRAMES_BUNDLE, PollingFrame.class);
+ processPollingFrames(pollingFrames);
}
- processPollingFrames(pollingFrames);
break;
default:
super.handleMessage(msg);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt b/nfc/java/android/nfc/cardemulation/PollingFrame.aidl
index b84b01e66955..8e09f8baaff2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibInputLog.kt
+++ b/nfc/java/android/nfc/cardemulation/PollingFrame.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -14,12 +14,6 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.dagger
+package android.nfc.cardemulation;
-import javax.inject.Qualifier
-
-/** Wifi logs for inputs into [WifiRepositoryViaTrackerLib]. */
-@Qualifier
-@MustBeDocumented
-@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
-annotation class WifiTrackerLibInputLog
+parcelable PollingFrame; \ No newline at end of file
diff --git a/nfc/java/android/nfc/cardemulation/PollingFrame.java b/nfc/java/android/nfc/cardemulation/PollingFrame.java
index c6861bf97443..b52faba79ed7 100644
--- a/nfc/java/android/nfc/cardemulation/PollingFrame.java
+++ b/nfc/java/android/nfc/cardemulation/PollingFrame.java
@@ -101,47 +101,37 @@ public final class PollingFrame implements Parcelable{
/**
* KEY_POLLING_LOOP_TYPE is the Bundle key for the type of
* polling loop frame in the Bundle included in MSG_POLLING_LOOP.
- *
- * @hide
*/
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final String KEY_POLLING_LOOP_TYPE = "android.nfc.cardemulation.TYPE";
+ private static final String KEY_POLLING_LOOP_TYPE = "android.nfc.cardemulation.TYPE";
/**
* KEY_POLLING_LOOP_DATA is the Bundle key for the raw data of captured from
* the polling loop frame in the Bundle included in MSG_POLLING_LOOP.
- *
- * @hide
*/
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final String KEY_POLLING_LOOP_DATA = "android.nfc.cardemulation.DATA";
+ private static final String KEY_POLLING_LOOP_DATA = "android.nfc.cardemulation.DATA";
/**
* KEY_POLLING_LOOP_GAIN is the Bundle key for the field strength of
* the polling loop frame in the Bundle included in MSG_POLLING_LOOP.
- *
- * @hide
- */
+ */
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final String KEY_POLLING_LOOP_GAIN = "android.nfc.cardemulation.GAIN";
+ private static final String KEY_POLLING_LOOP_GAIN = "android.nfc.cardemulation.GAIN";
/**
* KEY_POLLING_LOOP_TIMESTAMP is the Bundle key for the timestamp of
* the polling loop frame in the Bundle included in MSG_POLLING_LOOP.
- *
- * @hide
- */
+ */
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final String KEY_POLLING_LOOP_TIMESTAMP = "android.nfc.cardemulation.TIMESTAMP";
+ private static final String KEY_POLLING_LOOP_TIMESTAMP = "android.nfc.cardemulation.TIMESTAMP";
/**
* KEY_POLLING_LOOP_TIMESTAMP is the Bundle key for whether this polling frame triggered
* autoTransact in the Bundle included in MSG_POLLING_LOOP.
- *
- * @hide
- */
+ */
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final String KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT =
+ private static final String KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT =
"android.nfc.cardemulation.TRIGGERED_AUTOTRANSACT";
@@ -151,7 +141,7 @@ public final class PollingFrame implements Parcelable{
private final int mGain;
@DurationMillisLong
private final long mTimestamp;
- private final boolean mTriggeredAutoTransact;
+ private boolean mTriggeredAutoTransact;
public static final @NonNull Parcelable.Creator<PollingFrame> CREATOR =
new Parcelable.Creator<>() {
@@ -166,7 +156,7 @@ public final class PollingFrame implements Parcelable{
}
};
- PollingFrame(Bundle frame) {
+ private PollingFrame(Bundle frame) {
mType = frame.getInt(KEY_POLLING_LOOP_TYPE);
byte[] data = frame.getByteArray(KEY_POLLING_LOOP_DATA);
mData = (data == null) ? new byte[0] : data;
@@ -239,6 +229,13 @@ public final class PollingFrame implements Parcelable{
}
/**
+ * @hide
+ */
+ public void setTriggeredAutoTransact(boolean triggeredAutoTransact) {
+ mTriggeredAutoTransact = triggeredAutoTransact;
+ }
+
+ /**
* Returns whether this frame triggered the device to automatically disable observe mode and
* allow one transaction.
*/
@@ -257,11 +254,9 @@ public final class PollingFrame implements Parcelable{
}
/**
- *
- * @hide
* @return a Bundle representing this frame
*/
- public Bundle toBundle() {
+ private Bundle toBundle() {
Bundle frame = new Bundle();
frame.putInt(KEY_POLLING_LOOP_TYPE, getType());
if (getVendorSpecificGain() != -1) {
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
index c8801bb5ded2..a5bb34f4422b 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
@@ -188,7 +188,12 @@ public class CompanionDeviceDiscoveryService extends Service {
filter(allFilters, BluetoothLeDeviceFilter.class);
final List<WifiDeviceFilter> wifiFilters = filter(allFilters, WifiDeviceFilter.class);
- checkBoundDevicesIfNeeded(request, btFilters);
+ // No need to startDiscovery if the device is already bound or connected for
+ // singleDevice dialog.
+ if (checkBoundDevicesIfNeeded(request, btFilters)) {
+ stopSelf();
+ return;
+ }
// If no filters are specified: look for everything.
final boolean forceStartScanningAll = isEmpty(allFilters);
@@ -248,33 +253,37 @@ public class CompanionDeviceDiscoveryService extends Service {
stopSelf();
}
- private void checkBoundDevicesIfNeeded(@NonNull AssociationRequest request,
+ private boolean checkBoundDevicesIfNeeded(@NonNull AssociationRequest request,
@NonNull List<BluetoothDeviceFilter> btFilters) {
// If filtering to get single device by mac address, also search in the set of already
// bonded devices to allow linking those directly
- if (btFilters.isEmpty() || !request.isSingleDevice()) return;
+ if (btFilters.isEmpty() || !request.isSingleDevice()) return false;
final BluetoothDeviceFilter singleMacAddressFilter =
find(btFilters, filter -> !TextUtils.isEmpty(filter.getAddress()));
- if (singleMacAddressFilter == null) return;
+ if (singleMacAddressFilter == null) return false;
- findAndReportMatches(mBtAdapter.getBondedDevices(), btFilters);
- findAndReportMatches(mBtManager.getConnectedDevices(BluetoothProfile.GATT), btFilters);
- findAndReportMatches(
- mBtManager.getConnectedDevices(BluetoothProfile.GATT_SERVER), btFilters);
+ return findAndReportMatches(mBtAdapter.getBondedDevices(), btFilters)
+ || findAndReportMatches(mBtManager.getConnectedDevices(
+ BluetoothProfile.GATT), btFilters)
+ || findAndReportMatches(mBtManager.getConnectedDevices(
+ BluetoothProfile.GATT_SERVER), btFilters);
}
- private void findAndReportMatches(@Nullable Collection<BluetoothDevice> devices,
+ private boolean findAndReportMatches(@Nullable Collection<BluetoothDevice> devices,
@NonNull List<BluetoothDeviceFilter> filters) {
- if (devices == null) return;
+ if (devices == null) return false;
for (BluetoothDevice device : devices) {
final DeviceFilterPair<BluetoothDevice> match = findMatch(device, filters);
if (match != null) {
onDeviceFound(match);
+ return true;
}
}
+
+ return false;
}
private BluetoothBroadcastReceiver startBtScanningIfNeeded(
diff --git a/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java b/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java
deleted file mode 100644
index fa4d6afc03d3..000000000000
--- a/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * 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 android.utils;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import java.io.File;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Copied over from frameworks/base/core/java/com/android/internal/util/ArrayUtils.java
- *
- * @hide
- */
-public class ArrayUtils {
- private ArrayUtils() { /* cannot be instantiated */ }
- public static final File[] EMPTY_FILE = new File[0];
-
-
- /**
- * Return first index of {@code value} in {@code array}, or {@code -1} if
- * not found.
- */
- public static <T> int indexOf(@Nullable T[] array, T value) {
- if (array == null) return -1;
- for (int i = 0; i < array.length; i++) {
- if (Objects.equals(array[i], value)) return i;
- }
- return -1;
- }
-
- /** @hide */
- public static @NonNull File[] defeatNullable(@Nullable File[] val) {
- return (val != null) ? val : EMPTY_FILE;
- }
-
- /**
- * Checks if given array is null or has zero elements.
- */
- public static boolean isEmpty(@Nullable int[] array) {
- return array == null || array.length == 0;
- }
-
- /**
- * True if the byte array is null or has length 0.
- */
- public static boolean isEmpty(@Nullable byte[] array) {
- return array == null || array.length == 0;
- }
-
- /**
- * Converts from List of bytes to byte array
- * @param list
- * @return byte[]
- */
- public static byte[] toPrimitive(List<byte[]> list) {
- if (list.size() == 0) {
- return new byte[0];
- }
- int byteLen = list.get(0).length;
- byte[] array = new byte[list.size() * byteLen];
- for (int i = 0; i < list.size(); i++) {
- for (int j = 0; j < list.get(i).length; j++) {
- array[i * byteLen + j] = list.get(i)[j];
- }
- }
- return array;
- }
-
- /**
- * Adds value to given array if not already present, providing set-like
- * behavior.
- */
- public static @NonNull int[] appendInt(@Nullable int[] cur, int val) {
- return appendInt(cur, val, false);
- }
-
- /**
- * Adds value to given array.
- */
- public static @NonNull int[] appendInt(@Nullable int[] cur, int val,
- boolean allowDuplicates) {
- if (cur == null) {
- return new int[] { val };
- }
- final int n = cur.length;
- if (!allowDuplicates) {
- for (int i = 0; i < n; i++) {
- if (cur[i] == val) {
- return cur;
- }
- }
- }
- int[] ret = new int[n + 1];
- System.arraycopy(cur, 0, ret, 0, n);
- ret[n] = val;
- return ret;
- }
-}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java b/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java
deleted file mode 100644
index afcf6895fd0d..000000000000
--- a/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * * 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 android.utils;
-
-import android.annotation.NonNull;
-import android.os.Handler;
-import android.os.HandlerThread;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.concurrent.Executor;
-
-/**
- * Thread for asynchronous event processing. This thread is configured as
- * {@link android.os.Process#THREAD_PRIORITY_BACKGROUND}, which means fewer CPU
- * resources will be dedicated to it, and it will "have less chance of impacting
- * the responsiveness of the user interface."
- * <p>
- * This thread is best suited for tasks that the user is not actively waiting
- * for, or for tasks that the user expects to be executed eventually.
- *
- * @see com.android.internal.os.BackgroundThread
- *
- * TODO: b/326916057 depend on modules-utils-backgroundthread instead
- * @hide
- */
-public final class BackgroundThread extends HandlerThread {
- private static final Object sLock = new Object();
-
- @GuardedBy("sLock")
- private static BackgroundThread sInstance;
- @GuardedBy("sLock")
- private static Handler sHandler;
- @GuardedBy("sLock")
- private static HandlerExecutor sHandlerExecutor;
-
- private BackgroundThread() {
- super(BackgroundThread.class.getName(), android.os.Process.THREAD_PRIORITY_BACKGROUND);
- }
-
- @GuardedBy("sLock")
- private static void ensureThreadLocked() {
- if (sInstance == null) {
- sInstance = new BackgroundThread();
- sInstance.start();
- sHandler = new Handler(sInstance.getLooper());
- sHandlerExecutor = new HandlerExecutor(sHandler);
- }
- }
-
- /**
- * Get the singleton instance of this class.
- *
- * @return the singleton instance of this class
- */
- @NonNull
- public static BackgroundThread get() {
- synchronized (sLock) {
- ensureThreadLocked();
- return sInstance;
- }
- }
-
- /**
- * Get the singleton {@link Handler} for this class.
- *
- * @return the singleton {@link Handler} for this class.
- */
- @NonNull
- public static Handler getHandler() {
- synchronized (sLock) {
- ensureThreadLocked();
- return sHandler;
- }
- }
-
- /**
- * Get the singleton {@link Executor} for this class.
- *
- * @return the singleton {@link Executor} for this class.
- */
- @NonNull
- public static Executor getExecutor() {
- synchronized (sLock) {
- ensureThreadLocked();
- return sHandlerExecutor;
- }
- }
-}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java b/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java
deleted file mode 100644
index e4923bfc4ecb..000000000000
--- a/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * 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 android.utils;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import java.io.BufferedInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * Bits and pieces copied from hidden API of android.os.FileUtils.
- *
- * @hide
- */
-public class FileUtils {
- /**
- * Read a text file into a String, optionally limiting the length.
- *
- * @param file to read (will not seek, so things like /proc files are OK)
- * @param max length (positive for head, negative of tail, 0 for no limit)
- * @param ellipsis to add of the file was truncated (can be null)
- * @return the contents of the file, possibly truncated
- * @throws IOException if something goes wrong reading the file
- * @hide
- */
- public static @Nullable String readTextFile(@Nullable File file, @Nullable int max,
- @Nullable String ellipsis) throws IOException {
- InputStream input = new FileInputStream(file);
- // wrapping a BufferedInputStream around it because when reading /proc with unbuffered
- // input stream, bytes read not equal to buffer size is not necessarily the correct
- // indication for EOF; but it is true for BufferedInputStream due to its implementation.
- BufferedInputStream bis = new BufferedInputStream(input);
- try {
- long size = file.length();
- if (max > 0 || (size > 0 && max == 0)) { // "head" mode: read the first N bytes
- if (size > 0 && (max == 0 || size < max)) max = (int) size;
- byte[] data = new byte[max + 1];
- int length = bis.read(data);
- if (length <= 0) return "";
- if (length <= max) return new String(data, 0, length);
- if (ellipsis == null) return new String(data, 0, max);
- return new String(data, 0, max) + ellipsis;
- } else if (max < 0) { // "tail" mode: keep the last N
- int len;
- boolean rolled = false;
- byte[] last = null;
- byte[] data = null;
- do {
- if (last != null) rolled = true;
- byte[] tmp = last;
- last = data;
- data = tmp;
- if (data == null) data = new byte[-max];
- len = bis.read(data);
- } while (len == data.length);
-
- if (last == null && len <= 0) return "";
- if (last == null) return new String(data, 0, len);
- if (len > 0) {
- rolled = true;
- System.arraycopy(last, len, last, 0, last.length - len);
- System.arraycopy(data, 0, last, last.length - len, len);
- }
- if (ellipsis == null || !rolled) return new String(last);
- return ellipsis + new String(last);
- } else { // "cat" mode: size unknown, read it all in streaming fashion
- ByteArrayOutputStream contents = new ByteArrayOutputStream();
- int len;
- byte[] data = new byte[1024];
- do {
- len = bis.read(data);
- if (len > 0) contents.write(data, 0, len);
- } while (len == data.length);
- return contents.toString();
- }
- } finally {
- bis.close();
- input.close();
- }
- }
-
- /**
- * Perform an fsync on the given FileOutputStream. The stream at this
- * point must be flushed but not yet closed.
- *
- * @hide
- */
- public static boolean sync(FileOutputStream stream) {
- try {
- if (stream != null) {
- stream.getFD().sync();
- }
- return true;
- } catch (IOException e) {
- }
- return false;
- }
-
- /**
- * List the files in the directory or return empty file.
- *
- * @hide
- */
- public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) {
- return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles())
- : ArrayUtils.EMPTY_FILE;
- }
-}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java b/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java
deleted file mode 100644
index fdb15e2333d5..000000000000
--- a/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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 android.utils;
-
-import android.annotation.NonNull;
-import android.os.Handler;
-
-import java.util.Objects;
-import java.util.concurrent.Executor;
-import java.util.concurrent.RejectedExecutionException;
-
-/**
- * An adapter {@link Executor} that posts all executed tasks onto the given
- * {@link Handler}.
- *
- * TODO: b/326916057 depend on modules-utils-backgroundthread instead
- * @hide
- */
-public class HandlerExecutor implements Executor {
- private final Handler mHandler;
-
- public HandlerExecutor(@NonNull Handler handler) {
- mHandler = Objects.requireNonNull(handler);
- }
-
- @Override
- public void execute(Runnable command) {
- if (!mHandler.post(command)) {
- throw new RejectedExecutionException(mHandler + " is shutting down");
- }
- }
-}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java b/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java
deleted file mode 100644
index 5cdc2536129a..000000000000
--- a/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * 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 android.utils;
-
-import libcore.util.EmptyArray;
-
-import java.util.NoSuchElementException;
-
-/**
- * Copied from frameworks/base/core/java/android/util/LongArrayQueue.java
- *
- * @hide
- */
-public class LongArrayQueue {
-
- private long[] mValues;
- private int mSize;
- private int mHead;
- private int mTail;
-
- private long[] newUnpaddedLongArray(int num) {
- return new long[num];
- }
- /**
- * Initializes a queue with the given starting capacity.
- *
- * @param initialCapacity the capacity.
- */
- public LongArrayQueue(int initialCapacity) {
- if (initialCapacity == 0) {
- mValues = EmptyArray.LONG;
- } else {
- mValues = newUnpaddedLongArray(initialCapacity);
- }
- mSize = 0;
- mHead = mTail = 0;
- }
-
- /**
- * Initializes a queue with default starting capacity.
- */
- public LongArrayQueue() {
- this(16);
- }
-
- /** @hide */
- public static int growSize(int currentSize) {
- return currentSize <= 4 ? 8 : currentSize * 2;
- }
-
- private void grow() {
- if (mSize < mValues.length) {
- throw new IllegalStateException("Queue not full yet!");
- }
- final int newSize = growSize(mSize);
- final long[] newArray = newUnpaddedLongArray(newSize);
- final int r = mValues.length - mHead; // Number of elements on and to the right of head.
- System.arraycopy(mValues, mHead, newArray, 0, r);
- System.arraycopy(mValues, 0, newArray, r, mHead);
- mValues = newArray;
- mHead = 0;
- mTail = mSize;
- }
-
- /**
- * Returns the number of elements in the queue.
- */
- public int size() {
- return mSize;
- }
-
- /**
- * Removes all elements from this queue.
- */
- public void clear() {
- mSize = 0;
- mHead = mTail = 0;
- }
-
- /**
- * Adds a value to the tail of the queue.
- *
- * @param value the value to be added.
- */
- public void addLast(long value) {
- if (mSize == mValues.length) {
- grow();
- }
- mValues[mTail] = value;
- mTail = (mTail + 1) % mValues.length;
- mSize++;
- }
-
- /**
- * Removes an element from the head of the queue.
- *
- * @return the element at the head of the queue.
- * @throws NoSuchElementException if the queue is empty.
- */
- public long removeFirst() {
- if (mSize == 0) {
- throw new NoSuchElementException("Queue is empty!");
- }
- final long ret = mValues[mHead];
- mHead = (mHead + 1) % mValues.length;
- mSize--;
- return ret;
- }
-
- /**
- * Returns the element at the given position from the head of the queue, where 0 represents the
- * head of the queue.
- *
- * @param position the position from the head of the queue.
- * @return the element found at the given position.
- * @throws IndexOutOfBoundsException if {@code position} < {@code 0} or
- * {@code position} >= {@link #size()}
- */
- public long get(int position) {
- if (position < 0 || position >= mSize) {
- throw new IndexOutOfBoundsException("Index " + position
- + " not valid for a queue of size " + mSize);
- }
- final int index = (mHead + position) % mValues.length;
- return mValues[index];
- }
-
- /**
- * Returns the element at the head of the queue, without removing it.
- *
- * @return the element at the head of the queue.
- * @throws NoSuchElementException if the queue is empty
- */
- public long peekFirst() {
- if (mSize == 0) {
- throw new NoSuchElementException("Queue is empty!");
- }
- return mValues[mHead];
- }
-
- /**
- * Returns the element at the tail of the queue.
- *
- * @return the element at the tail of the queue.
- * @throws NoSuchElementException if the queue is empty.
- */
- public long peekLast() {
- if (mSize == 0) {
- throw new NoSuchElementException("Queue is empty!");
- }
- final int index = (mTail == 0) ? mValues.length - 1 : mTail - 1;
- return mValues[index];
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public String toString() {
- if (mSize <= 0) {
- return "{}";
- }
-
- final StringBuilder buffer = new StringBuilder(mSize * 64);
- buffer.append('{');
- buffer.append(get(0));
- for (int i = 1; i < mSize; i++) {
- buffer.append(", ");
- buffer.append(get(i));
- }
- buffer.append('}');
- return buffer.toString();
- }
-}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java b/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java
deleted file mode 100644
index dbbef61f6777..000000000000
--- a/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * 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 android.utils;
-
-import android.annotation.NonNull;
-import android.system.ErrnoException;
-import android.system.Os;
-
-import com.android.modules.utils.TypedXmlPullParser;
-
-import libcore.util.XmlObjectFactory;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.BufferedInputStream;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * Copied over partly from frameworks/base/core/java/com/android/internal/util/XmlUtils.java
- *
- * @hide
- */
-public class XmlUtils {
-
- private static final String STRING_ARRAY_SEPARATOR = ":";
-
- /** @hide */
- public static final void beginDocument(XmlPullParser parser, String firstElementName)
- throws XmlPullParserException, IOException {
- int type;
- while ((type = parser.next()) != parser.START_TAG
- && type != parser.END_DOCUMENT) {
- // Do nothing
- }
-
- if (type != parser.START_TAG) {
- throw new XmlPullParserException("No start tag found");
- }
-
- if (!parser.getName().equals(firstElementName)) {
- throw new XmlPullParserException("Unexpected start tag: found " + parser.getName()
- + ", expected " + firstElementName);
- }
- }
-
- /** @hide */
- public static boolean nextElementWithin(XmlPullParser parser, int outerDepth)
- throws IOException, XmlPullParserException {
- for (;;) {
- int type = parser.next();
- if (type == XmlPullParser.END_DOCUMENT
- || (type == XmlPullParser.END_TAG && parser.getDepth() == outerDepth)) {
- return false;
- }
- if (type == XmlPullParser.START_TAG
- && parser.getDepth() == outerDepth + 1) {
- return true;
- }
- }
- }
-
- private static XmlPullParser newPullParser() {
- try {
- XmlPullParser parser = XmlObjectFactory.newXmlPullParser();
- parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true);
- parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
- return parser;
- } catch (XmlPullParserException e) {
- throw new AssertionError();
- }
- }
-
- /** @hide */
- public static @NonNull TypedXmlPullParser resolvePullParser(@NonNull InputStream in)
- throws IOException {
- final byte[] magic = new byte[4];
- if (in instanceof FileInputStream) {
- try {
- Os.pread(((FileInputStream) in).getFD(), magic, 0, magic.length, 0);
- } catch (ErrnoException e) {
- throw e.rethrowAsIOException();
- }
- } else {
- if (!in.markSupported()) {
- in = new BufferedInputStream(in);
- }
- in.mark(8);
- in.read(magic);
- in.reset();
- }
-
- final TypedXmlPullParser xml;
- xml = (TypedXmlPullParser) newPullParser();
- try {
- xml.setInput(in, "UTF_8");
- } catch (XmlPullParserException e) {
- throw new IOException(e);
- }
- return xml;
- }
-}
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
index e998fe8fc8d9..2aff2c30939f 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
@@ -34,7 +34,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_alignParentStart="true"
- android:paddingLeft="@dimen/autofill_view_left_padding"
+ android:paddingStart="@dimen/autofill_view_left_padding"
app:tint="?androidprv:attr/materialColorOnSurface"
android:background="@null"/>
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 9db681a3565d..46a51389214f 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -121,16 +121,10 @@
<string name="get_dialog_title_use_passkey_for">Use your saved passkey for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
<!-- This appears as the title of the modal bottom sheet asking for user confirmation to use the single previously saved password to sign in to the app. [CHAR LIMIT=200] -->
<string name="get_dialog_title_use_password_for">Use your saved password for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
- <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the get flow. [CHAR LIMIT=200] -->
- <string name="get_dialog_title_single_tap_for">Use your screen lock to sign in to <xliff:g id="app_name" example="Shrine">%1$s</xliff:g> with <xliff:g id="username" example="beckett-bakery@gmail.com">%2$s</xliff:g></string>
<!-- This appears as the title of the dialog asking for user confirmation to use the single user credential (previously saved or to be created) to sign in to the app. [CHAR LIMIT=200] -->
<string name="get_dialog_title_use_sign_in_for">Use your account for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
<!-- This appears as the description of the modal bottom sheet asking for user confirmation to use the biometric screen embedded within credential manager for passkey authentication. [CHAR LIMIT=200] -->
- <string name="get_dialog_description_single_tap_passkey">Sign in to <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> with your saved passkey for <xliff:g id="username" example="beckett@gmail.com">%2$s</xliff:g>.</string>
- <!-- This appears as the description of the modal bottom sheet asking for user confirmation to use the biometric screen embedded within credential manager for password authentication. [CHAR LIMIT=200] -->
- <string name="get_dialog_description_single_tap_password">Sign in to <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> with your saved password for <xliff:g id="username" example="beckett@gmail.com">%2$s</xliff:g>.</string>
- <!-- This appears as the description of the modal bottom sheet asking for user confirmation to use the biometric screen embedded within credential manager for saved sign-in authentication. [CHAR LIMIT=200] -->
- <string name="get_dialog_description_single_tap_saved_sign_in">Sign in to <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> with your saved sign-in info for <xliff:g id="username" example="beckett@gmail.com">%2$s</xliff:g>.</string>
+ <string name="get_dialog_description_single_tap">Use your screen lock to sign in to <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> with <xliff:g id="username" example="beckett@gmail.com">%2$s</xliff:g></string>
<!-- This appears as the title of the dialog asking for user confirmation to unlock / authenticate (e.g. via fingerprint, faceId, passcode etc.) so that we can retrieve their sign-in options. [CHAR LIMIT=200] -->
<string name="get_dialog_title_unlock_options_for">Unlock sign-in options for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
<!-- This appears as the title of the dialog asking for user to make a choice from multiple previously saved passkey to sign in to the app. [CHAR LIMIT=200] -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 429bdbf5959b..7bc32419e070 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -317,6 +317,14 @@ class CredentialSelectorViewModel(
)
}
+ fun createFlowOnMoreOptionsOnlySelectedOnCreationSelection() {
+ uiState = uiState.copy(
+ createCredentialUiState = uiState.createCredentialUiState?.copy(
+ currentScreenState = CreateScreenState.MORE_OPTIONS_SELECTION_ONLY,
+ )
+ )
+ }
+
fun createFlowOnBackCreationSelectionButtonSelected() {
uiState = uiState.copy(
createCredentialUiState = uiState.createCredentialUiState?.copy(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
index 95f49e95d3ce..0d19a45ab82a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
@@ -34,7 +34,6 @@ import com.android.credentialmanager.getflow.ProviderDisplayInfo
import com.android.credentialmanager.getflow.RequestDisplayInfo
import com.android.credentialmanager.getflow.generateDisplayTitleTextResCode
import com.android.credentialmanager.model.BiometricRequestInfo
-import com.android.credentialmanager.model.CredentialType
import com.android.credentialmanager.model.EntryInfo
import com.android.credentialmanager.model.creation.CreateOptionInfo
import com.android.credentialmanager.model.get.CredentialEntryInfo
@@ -210,7 +209,7 @@ private fun runBiometricFlow(
onCancelFlowAndFinish: () -> Unit
) {
try {
- if (onlyUsingDeviceCredentials(biometricDisplayInfo, context)) {
+ if (!canCallBiometricPrompt(biometricDisplayInfo, context)) {
onBiometricFailureFallback(biometricFlowType)
return
}
@@ -235,8 +234,7 @@ private fun runBiometricFlow(
} else {
biometricPrompt.authenticate(cancellationSignal, executor, callback)
}
- } catch (e: Exception) {
- // TODO(b/334923201) : Specialize exception catching
+ } catch (e: IllegalArgumentException) {
Log.w(TAG, "Calling the biometric prompt API failed with: /n${e.localizedMessage}\n")
onBiometricFailureFallback(biometricFlowType)
}
@@ -251,40 +249,40 @@ private fun getCryptoOpId(biometricDisplayInfo: BiometricDisplayInfo): Int? {
* consistency because for biometrics to exist, **device credentials must exist**. Thus, fallbacks
* occur if *only* device credentials are available, to avoid going right into the PIN screen.
* Note that if device credential is the only available modality but not requested, or if none
- * of the requested modalities are available, we propagate the error to the provider instead of
- * falling back and expect them to handle it as they would prior.
- * // TODO(b/334197980) : Finalize error propagation/not propagation in real use cases
+ * of the requested modalities are available, we fallback to the normal flow to ensure a selector
+ * shows up.
+ * // TODO(b/334197980) : While we already fallback in cases the selector doesn't show, confirm
+ * // final plan.
*/
-private fun onlyUsingDeviceCredentials(
+private fun canCallBiometricPrompt(
biometricDisplayInfo: BiometricDisplayInfo,
context: Context
): Boolean {
val allowedAuthenticators = biometricDisplayInfo.biometricRequestInfo.allowedAuthenticators
if (allowedAuthenticators == BiometricManager.Authenticators.DEVICE_CREDENTIAL) {
- return true
+ return false
}
- val allowedAuthContainsDeviceCredential = containsBiometricAuthenticatorWithDeviceCredentials(
- allowedAuthenticators)
+ val biometricManager = context.getSystemService(Context.BIOMETRIC_SERVICE) as BiometricManager
- if (!allowedAuthContainsDeviceCredential) {
- // At this point, allowed authenticators is requesting biometrics without device creds.
- // Thus, a fallback mechanism will be displayed via our own negative button - "cancel".
- // Beyond this point, fallbacks will occur if none of the stronger authenticators can
- // be used.
+ if (biometricManager.canAuthenticate(allowedAuthenticators) !=
+ BiometricManager.BIOMETRIC_SUCCESS) {
return false
}
- val biometricManager = context.getSystemService(Context.BIOMETRIC_SERVICE) as BiometricManager
+ if (ifOnlySupportsAtMostDeviceCredentials(biometricManager)) return false
- if (allowedAuthContainsDeviceCredential &&
- biometricManager.canAuthenticate(Authenticators.BIOMETRIC_WEAK) !=
+ return true
+}
+
+private fun ifOnlySupportsAtMostDeviceCredentials(biometricManager: BiometricManager): Boolean {
+ if (biometricManager.canAuthenticate(Authenticators.BIOMETRIC_WEAK) !=
BiometricManager.BIOMETRIC_SUCCESS &&
biometricManager.canAuthenticate(Authenticators.BIOMETRIC_STRONG) !=
- BiometricManager.BIOMETRIC_SUCCESS) {
+ BiometricManager.BIOMETRIC_SUCCESS
+ ) {
return true
}
-
return false
}
@@ -481,16 +479,7 @@ private fun retrieveBiometricGetDisplayValues(
)
descriptionText = context.getString(
- when (singleEntryType) {
- CredentialType.PASSKEY ->
- R.string.get_dialog_description_single_tap_passkey
-
- CredentialType.PASSWORD ->
- R.string.get_dialog_description_single_tap_password
-
- CredentialType.UNKNOWN ->
- R.string.get_dialog_description_single_tap_saved_sign_in
- },
+ R.string.get_dialog_description_single_tap,
getRequestDisplayInfo.appName,
username
)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
index 149c14a24085..2c3c63bea95f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -28,7 +28,6 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.outlined.Lock
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
@@ -47,7 +46,6 @@ import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.input.PasswordVisualTransformation
@@ -55,7 +53,6 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import com.android.compose.theme.LocalAndroidColorScheme
-import com.android.credentialmanager.R
import com.android.credentialmanager.ui.theme.EntryShape
import com.android.credentialmanager.ui.theme.Shapes
@@ -321,6 +318,8 @@ fun CtaButtonRow(
fun MoreOptionTopAppBar(
text: String,
onNavigationIconClicked: () -> Unit,
+ navigationIcon: ImageVector,
+ navigationIconContentDescription: String,
bottomPadding: Dp,
) {
Row(
@@ -336,40 +335,6 @@ fun MoreOptionTopAppBar(
contentAlignment = Alignment.Center,
) {
Icon(
- imageVector = Icons.Filled.ArrowBack,
- contentDescription = stringResource(
- R.string.accessibility_back_arrow_button
- ),
- modifier = Modifier.size(24.dp).autoMirrored(),
- tint = LocalAndroidColorScheme.current.onSurfaceVariant,
- )
- }
- }
- LargeTitleText(text = text, modifier = Modifier.padding(horizontal = 4.dp))
- }
-}
-
-@Composable
-fun MoreOptionTopAppBarWithCustomNavigation(
- text: String,
- onNavigationIconClicked: () -> Unit,
- navigationIcon: ImageVector,
- navigationIconContentDescription: String,
- bottomPadding: Dp,
-) {
- Row(
- modifier = Modifier.padding(top = 12.dp, bottom = bottomPadding),
- verticalAlignment = Alignment.CenterVertically,
- ) {
- IconButton(
- modifier = Modifier.padding(top = 8.dp, bottom = 8.dp, start = 4.dp).size(48.dp),
- onClick = onNavigationIconClicked
- ) {
- Box(
- modifier = Modifier.size(48.dp),
- contentAlignment = Alignment.Center,
- ) {
- Icon(
imageVector = navigationIcon,
contentDescription = navigationIconContentDescription,
modifier = Modifier.size(24.dp).autoMirrored(),
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index a0915d22b613..282a1b5736ac 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -32,6 +32,8 @@ import androidx.compose.material3.Divider
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.NewReleases
import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.outlined.QrCodeScanner
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -107,7 +109,7 @@ fun CreateCredentialScreen(
onCancelFlowAndFinish = viewModel::onUserCancel,
onIllegalScreenStateAndFinish = viewModel::onIllegalUiState,
onMoreOptionSelected =
- viewModel::createFlowOnMoreOptionsSelectedOnCreationSelection,
+ viewModel::createFlowOnMoreOptionsOnlySelectedOnCreationSelection,
requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
enabledProviderInfo = createCredentialUiState
.activeEntry?.activeProvider!!,
@@ -120,6 +122,41 @@ fun CreateCredentialScreen(
onBiometricPromptStateChange =
viewModel::onBiometricPromptStateChange
)
+ CreateScreenState.MORE_OPTIONS_SELECTION_ONLY -> MoreOptionsSelectionCard(
+ requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
+ enabledProviderList = createCredentialUiState.enabledProviders,
+ disabledProviderList = createCredentialUiState.disabledProviders,
+ sortedCreateOptionsPairs =
+ createCredentialUiState.sortedCreateOptionsPairs,
+ onBackCreationSelectionButtonSelected =
+ viewModel::createFlowOnBackCreationSelectionButtonSelected,
+ onOptionSelected =
+ viewModel::createFlowOnEntrySelectedFromMoreOptionScreen,
+ onDisabledProvidersSelected =
+ viewModel::createFlowOnLaunchSettings,
+ onRemoteEntrySelected = viewModel::createFlowOnEntrySelected,
+ onLog = { viewModel.logUiEvent(it) },
+ customTopAppBar = { MoreOptionTopAppBar(
+ text = stringResource(
+ R.string.save_credential_to_title,
+ when (createCredentialUiState.requestDisplayInfo
+ .type) {
+ CredentialType.PASSKEY ->
+ stringResource(R.string.passkey)
+ CredentialType.PASSWORD ->
+ stringResource(R.string.password)
+ CredentialType.UNKNOWN -> stringResource(
+ R.string.sign_in_info)
+ }
+ ),
+ onNavigationIconClicked = viewModel::onUserCancel,
+ bottomPadding = 16.dp,
+ navigationIcon = Icons.Filled.Close,
+ navigationIconContentDescription = stringResource(
+ R.string.accessibility_close_button
+ )
+ )}
+ )
CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
enabledProviderList = createCredentialUiState.enabledProviders,
@@ -207,22 +244,31 @@ fun MoreOptionsSelectionCard(
onDisabledProvidersSelected: () -> Unit,
onRemoteEntrySelected: (EntryInfo) -> Unit,
onLog: @Composable (UiEventEnum) -> Unit,
+ customTopAppBar: (@Composable() () -> Unit)? = null
) {
SheetContainerCard(topAppBar = {
- MoreOptionTopAppBar(
- text = stringResource(
- R.string.save_credential_to_title,
- when (requestDisplayInfo.type) {
- CredentialType.PASSKEY ->
- stringResource(R.string.passkey)
- CredentialType.PASSWORD ->
- stringResource(R.string.password)
- CredentialType.UNKNOWN -> stringResource(R.string.sign_in_info)
- }
- ),
- onNavigationIconClicked = onBackCreationSelectionButtonSelected,
- bottomPadding = 16.dp,
- )
+ if (customTopAppBar != null) {
+ customTopAppBar()
+ } else {
+ MoreOptionTopAppBar(
+ text = stringResource(
+ R.string.save_credential_to_title,
+ when (requestDisplayInfo.type) {
+ CredentialType.PASSKEY ->
+ stringResource(R.string.passkey)
+ CredentialType.PASSWORD ->
+ stringResource(R.string.password)
+ CredentialType.UNKNOWN -> stringResource(R.string.sign_in_info)
+ }
+ ),
+ onNavigationIconClicked = onBackCreationSelectionButtonSelected,
+ bottomPadding = 16.dp,
+ navigationIcon = Icons.Filled.ArrowBack,
+ navigationIconContentDescription = stringResource(
+ R.string.accessibility_back_arrow_button
+ )
+ )
+ }
}) {
// bottom padding already
item {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index ddd4139b65b6..130937c70895 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -181,4 +181,5 @@ enum class CreateScreenState {
MORE_OPTIONS_SELECTION,
DEFAULT_PROVIDER_CONFIRMATION,
EXTERNAL_ONLY_SELECTION,
+ MORE_OPTIONS_SELECTION_ONLY,
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index ce4f402700d3..c98bb5ed17c1 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -32,6 +32,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.outlined.QrCodeScanner
import androidx.compose.material3.Divider
@@ -71,7 +72,6 @@ import com.android.credentialmanager.common.ui.HeadlineText
import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant
import com.android.credentialmanager.common.ui.ModalBottomSheet
import com.android.credentialmanager.common.ui.MoreOptionTopAppBar
-import com.android.credentialmanager.common.ui.MoreOptionTopAppBarWithCustomNavigation
import com.android.credentialmanager.common.ui.SheetContainerCard
import com.android.credentialmanager.common.ui.Snackbar
import com.android.credentialmanager.common.ui.SnackbarActionText
@@ -175,7 +175,7 @@ fun GetCredentialScreen(
onBackButtonClicked = viewModel::onUserCancel,
onCancel = viewModel::onUserCancel,
onLog = { viewModel.logUiEvent(it) },
- customTopBar = { MoreOptionTopAppBarWithCustomNavigation(
+ customTopBar = { MoreOptionTopAppBar(
text = stringResource(
R.string.get_dialog_title_sign_in_options),
onNavigationIconClicked = viewModel::onUserCancel,
@@ -683,7 +683,10 @@ fun AllSignInOptionCard(
text = stringResource(R.string.get_dialog_title_sign_in_options),
onNavigationIconClicked = onBackButtonClicked,
bottomPadding = 0.dp,
- )
+ navigationIcon = Icons.Filled.ArrowBack,
+ navigationIconContentDescription = stringResource(
+ R.string.accessibility_back_arrow_button
+ ))
}
}) {
var isFirstSection = true
diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp
index bd84b58aa0f4..79c810ca2611 100644
--- a/packages/PackageInstaller/Android.bp
+++ b/packages/PackageInstaller/Android.bp
@@ -46,6 +46,7 @@ android_app {
sdk_version: "system_current",
rename_resources_package: false,
static_libs: [
+ "xz-java",
"androidx.leanback_leanback",
"androidx.annotation_annotation",
"androidx.fragment_fragment",
@@ -78,6 +79,7 @@ android_app {
overrides: ["PackageInstaller"],
static_libs: [
+ "xz-java",
"androidx.leanback_leanback",
"androidx.fragment_fragment",
"androidx.lifecycle_lifecycle-livedata",
@@ -110,6 +112,7 @@ android_app {
overrides: ["PackageInstaller"],
static_libs: [
+ "xz-java",
"androidx.leanback_leanback",
"androidx.annotation_annotation",
"androidx.fragment_fragment",
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 05f4d6954a00..bf69d3ba7603 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -146,6 +146,17 @@
android:configChanges="mnc|mnc|touchscreen|navigation|screenLayout|screenSize|smallestScreenSize|orientation|locale|keyboard|keyboardHidden|fontScale|uiMode|layoutDirection|density"
android:exported="false" />
+ <!-- Wearable Components -->
+ <service android:name=".wear.WearPackageInstallerService"
+ android:permission="com.google.android.permission.INSTALL_WEARABLE_PACKAGES"
+ android:foregroundServiceType="systemExempted"
+ android:exported="true"/>
+
+ <provider android:name=".wear.WearPackageIconProvider"
+ android:authorities="com.google.android.packageinstaller.wear.provider"
+ android:grantUriPermissions="true"
+ android:exported="false" />
+
<receiver android:name="androidx.profileinstaller.ProfileInstallReceiver"
tools:node="remove" />
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java
new file mode 100644
index 000000000000..53a460dc18ca
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2016 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.packageinstaller.wear;
+
+import android.content.Context;
+import android.content.IntentSender;
+import android.content.pm.PackageInstaller;
+import android.os.Looper;
+import android.os.ParcelFileDescriptor;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Task that installs an APK. This must not be called on the main thread.
+ * This code is based off the Finsky/Wearsky implementation
+ */
+public class InstallTask {
+ private static final String TAG = "InstallTask";
+
+ private static final int DEFAULT_BUFFER_SIZE = 8192;
+
+ private final Context mContext;
+ private String mPackageName;
+ private ParcelFileDescriptor mParcelFileDescriptor;
+ private PackageInstallerImpl.InstallListener mCallback;
+ private PackageInstaller.Session mSession;
+ private IntentSender mCommitCallback;
+
+ private Exception mException = null;
+ private int mErrorCode = 0;
+ private String mErrorDesc = null;
+
+ public InstallTask(Context context, String packageName,
+ ParcelFileDescriptor parcelFileDescriptor,
+ PackageInstallerImpl.InstallListener callback, PackageInstaller.Session session,
+ IntentSender commitCallback) {
+ mContext = context;
+ mPackageName = packageName;
+ mParcelFileDescriptor = parcelFileDescriptor;
+ mCallback = callback;
+ mSession = session;
+ mCommitCallback = commitCallback;
+ }
+
+ public boolean isError() {
+ return mErrorCode != InstallerConstants.STATUS_SUCCESS || !TextUtils.isEmpty(mErrorDesc);
+ }
+
+ public void execute() {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ throw new IllegalStateException("This method cannot be called from the UI thread.");
+ }
+
+ OutputStream sessionStream = null;
+ try {
+ sessionStream = mSession.openWrite(mPackageName, 0, -1);
+
+ // 2b: Stream the asset to the installer. Note:
+ // Note: writeToOutputStreamFromAsset() always safely closes the input stream
+ writeToOutputStreamFromAsset(sessionStream);
+ mSession.fsync(sessionStream);
+ } catch (Exception e) {
+ mException = e;
+ mErrorCode = InstallerConstants.ERROR_INSTALL_COPY_STREAM;
+ mErrorDesc = "Could not write to stream";
+ } finally {
+ if (sessionStream != null) {
+ // 2c: close output stream
+ try {
+ sessionStream.close();
+ } catch (Exception e) {
+ // Ignore otherwise
+ if (mException == null) {
+ mException = e;
+ mErrorCode = InstallerConstants.ERROR_INSTALL_CLOSE_STREAM;
+ mErrorDesc = "Could not close session stream";
+ }
+ }
+ }
+ }
+
+ if (mErrorCode != InstallerConstants.STATUS_SUCCESS) {
+ // An error occurred, we're done
+ Log.e(TAG, "Exception while installing " + mPackageName + ": " + mErrorCode + ", "
+ + mErrorDesc + ", " + mException);
+ mSession.close();
+ mCallback.installFailed(mErrorCode, "[" + mPackageName + "]" + mErrorDesc);
+ } else {
+ // 3. Commit the session (this actually installs it.) Session map
+ // will be cleaned up in the callback.
+ mCallback.installBeginning();
+ mSession.commit(mCommitCallback);
+ mSession.close();
+ }
+ }
+
+ /**
+ * {@code PackageInstaller} works with streams. Get the {@code FileDescriptor}
+ * corresponding to the {@code Asset} and then write the contents into an
+ * {@code OutputStream} that is passed in.
+ * <br>
+ * The {@code FileDescriptor} is closed but the {@code OutputStream} is not closed.
+ */
+ private boolean writeToOutputStreamFromAsset(OutputStream outputStream) {
+ if (outputStream == null) {
+ mErrorCode = InstallerConstants.ERROR_INSTALL_COPY_STREAM_EXCEPTION;
+ mErrorDesc = "Got a null OutputStream.";
+ return false;
+ }
+
+ if (mParcelFileDescriptor == null || mParcelFileDescriptor.getFileDescriptor() == null) {
+ mErrorCode = InstallerConstants.ERROR_COULD_NOT_GET_FD;
+ mErrorDesc = "Could not get FD";
+ return false;
+ }
+
+ InputStream inputStream = null;
+ try {
+ byte[] inputBuf = new byte[DEFAULT_BUFFER_SIZE];
+ int bytesRead;
+ inputStream = new ParcelFileDescriptor.AutoCloseInputStream(mParcelFileDescriptor);
+
+ while ((bytesRead = inputStream.read(inputBuf)) > -1) {
+ if (bytesRead > 0) {
+ outputStream.write(inputBuf, 0, bytesRead);
+ }
+ }
+
+ outputStream.flush();
+ } catch (IOException e) {
+ mErrorCode = InstallerConstants.ERROR_INSTALL_APK_COPY_FAILURE;
+ mErrorDesc = "Reading from Asset FD or writing to temp file failed: " + e;
+ return false;
+ } finally {
+ safeClose(inputStream);
+ }
+
+ return true;
+ }
+
+ /**
+ * Quietly close a closeable resource (e.g. a stream or file). The input may already
+ * be closed and it may even be null.
+ */
+ public static void safeClose(Closeable resource) {
+ if (resource != null) {
+ try {
+ resource.close();
+ } catch (IOException ioe) {
+ // Catch and discard the error
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java
new file mode 100644
index 000000000000..3daf3d831d97
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 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.packageinstaller.wear;
+
+/**
+ * Constants for Installation / Uninstallation requests.
+ * Using the same values as Finsky/Wearsky code for consistency in user analytics of failures
+ */
+public class InstallerConstants {
+ /** Request succeeded */
+ public static final int STATUS_SUCCESS = 0;
+
+ /**
+ * The new PackageInstaller also returns a small set of less granular error codes, which
+ * we'll remap to the range -500 and below to keep away from existing installer codes
+ * (which run from -1 to -110).
+ */
+ public final static int ERROR_PACKAGEINSTALLER_BASE = -500;
+
+ public static final int ERROR_COULD_NOT_GET_FD = -603;
+ /** This node is not targeted by this request. */
+
+ /** The install did not complete because could not create PackageInstaller session */
+ public final static int ERROR_INSTALL_CREATE_SESSION = -612;
+ /** The install did not complete because could not open PackageInstaller session */
+ public final static int ERROR_INSTALL_OPEN_SESSION = -613;
+ /** The install did not complete because could not open PackageInstaller output stream */
+ public final static int ERROR_INSTALL_OPEN_STREAM = -614;
+ /** The install did not complete because of an exception while streaming bytes */
+ public final static int ERROR_INSTALL_COPY_STREAM_EXCEPTION = -615;
+ /** The install did not complete because of an unexpected exception from PackageInstaller */
+ public final static int ERROR_INSTALL_SESSION_EXCEPTION = -616;
+ /** The install did not complete because of an unexpected userActionRequired callback */
+ public final static int ERROR_INSTALL_USER_ACTION_REQUIRED = -617;
+ /** The install did not complete because of an unexpected broadcast (missing fields) */
+ public final static int ERROR_INSTALL_MALFORMED_BROADCAST = -618;
+ /** The install did not complete because of an error while copying from downloaded file */
+ public final static int ERROR_INSTALL_APK_COPY_FAILURE = -619;
+ /** The install did not complete because of an error while copying to the PackageInstaller
+ * output stream */
+ public final static int ERROR_INSTALL_COPY_STREAM = -620;
+ /** The install did not complete because of an error while closing the PackageInstaller
+ * output stream */
+ public final static int ERROR_INSTALL_CLOSE_STREAM = -621;
+} \ No newline at end of file
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java
new file mode 100644
index 000000000000..bdc22cf0e276
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 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.packageinstaller.wear;
+
+import android.content.Context;
+
+/**
+ * Factory that creates a Package Installer.
+ */
+public class PackageInstallerFactory {
+ private static PackageInstallerImpl sPackageInstaller;
+
+ /**
+ * Return the PackageInstaller shared object. {@code init} should have already been called.
+ */
+ public synchronized static PackageInstallerImpl getPackageInstaller(Context context) {
+ if (sPackageInstaller == null) {
+ sPackageInstaller = new PackageInstallerImpl(context);
+ }
+ return sPackageInstaller;
+ }
+} \ No newline at end of file
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
new file mode 100644
index 000000000000..1e37f15f714d
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2016 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.packageinstaller.wear;
+
+import android.annotation.TargetApi;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.pm.PackageInstaller;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Implementation of package manager installation using modern PackageInstaller api.
+ *
+ * Heavily copied from Wearsky/Finsky implementation
+ */
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+public class PackageInstallerImpl {
+ private static final String TAG = "PackageInstallerImpl";
+
+ /** Intent actions used for broadcasts from PackageInstaller back to the local receiver */
+ private static final String ACTION_INSTALL_COMMIT =
+ "com.android.vending.INTENT_PACKAGE_INSTALL_COMMIT";
+
+ private final Context mContext;
+ private final PackageInstaller mPackageInstaller;
+ private final Map<String, PackageInstaller.SessionInfo> mSessionInfoMap;
+ private final Map<String, PackageInstaller.Session> mOpenSessionMap;
+
+ public PackageInstallerImpl(Context context) {
+ mContext = context.getApplicationContext();
+ mPackageInstaller = mContext.getPackageManager().getPackageInstaller();
+
+ // Capture a map of known sessions
+ // This list will be pruned a bit later (stale sessions will be canceled)
+ mSessionInfoMap = new HashMap<String, PackageInstaller.SessionInfo>();
+ List<PackageInstaller.SessionInfo> mySessions = mPackageInstaller.getMySessions();
+ for (int i = 0; i < mySessions.size(); i++) {
+ PackageInstaller.SessionInfo sessionInfo = mySessions.get(i);
+ String packageName = sessionInfo.getAppPackageName();
+ PackageInstaller.SessionInfo oldInfo = mSessionInfoMap.put(packageName, sessionInfo);
+
+ // Checking for old info is strictly for logging purposes
+ if (oldInfo != null) {
+ Log.w(TAG, "Multiple sessions for " + packageName + " found. Removing " + oldInfo
+ .getSessionId() + " & keeping " + mySessions.get(i).getSessionId());
+ }
+ }
+ mOpenSessionMap = new HashMap<String, PackageInstaller.Session>();
+ }
+
+ /**
+ * This callback will be made after an installation attempt succeeds or fails.
+ */
+ public interface InstallListener {
+ /**
+ * This callback signals that preflight checks have succeeded and installation
+ * is beginning.
+ */
+ void installBeginning();
+
+ /**
+ * This callback signals that installation has completed.
+ */
+ void installSucceeded();
+
+ /**
+ * This callback signals that installation has failed.
+ */
+ void installFailed(int errorCode, String errorDesc);
+ }
+
+ /**
+ * This is a placeholder implementation that bundles an entire "session" into a single
+ * call. This will be replaced by more granular versions that allow longer session lifetimes,
+ * download progress tracking, etc.
+ *
+ * This must not be called on main thread.
+ */
+ public void install(final String packageName, ParcelFileDescriptor parcelFileDescriptor,
+ final InstallListener callback) {
+ // 0. Generic try/catch block because I am not really sure what exceptions (other than
+ // IOException) might be thrown by PackageInstaller and I want to handle them
+ // at least slightly gracefully.
+ try {
+ // 1. Create or recover a session, and open it
+ // Try recovery first
+ PackageInstaller.Session session = null;
+ PackageInstaller.SessionInfo sessionInfo = mSessionInfoMap.get(packageName);
+ if (sessionInfo != null) {
+ // See if it's openable, or already held open
+ session = getSession(packageName);
+ }
+ // If open failed, or there was no session, create a new one and open it.
+ // If we cannot create or open here, the failure is terminal.
+ if (session == null) {
+ try {
+ innerCreateSession(packageName);
+ } catch (IOException ioe) {
+ Log.e(TAG, "Can't create session for " + packageName + ": " + ioe.getMessage());
+ callback.installFailed(InstallerConstants.ERROR_INSTALL_CREATE_SESSION,
+ "Could not create session");
+ mSessionInfoMap.remove(packageName);
+ return;
+ }
+ sessionInfo = mSessionInfoMap.get(packageName);
+ try {
+ session = mPackageInstaller.openSession(sessionInfo.getSessionId());
+ mOpenSessionMap.put(packageName, session);
+ } catch (SecurityException se) {
+ Log.e(TAG, "Can't open session for " + packageName + ": " + se.getMessage());
+ callback.installFailed(InstallerConstants.ERROR_INSTALL_OPEN_SESSION,
+ "Can't open session");
+ mSessionInfoMap.remove(packageName);
+ return;
+ }
+ }
+
+ // 2. Launch task to handle file operations.
+ InstallTask task = new InstallTask( mContext, packageName, parcelFileDescriptor,
+ callback, session,
+ getCommitCallback(packageName, sessionInfo.getSessionId(), callback));
+ task.execute();
+ if (task.isError()) {
+ cancelSession(sessionInfo.getSessionId(), packageName);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Unexpected exception while installing: " + packageName + ": "
+ + e.getMessage());
+ callback.installFailed(InstallerConstants.ERROR_INSTALL_SESSION_EXCEPTION,
+ "Unexpected exception while installing " + packageName);
+ }
+ }
+
+ /**
+ * Retrieve an existing session. Will open if needed, but does not attempt to create.
+ */
+ private PackageInstaller.Session getSession(String packageName) {
+ // Check for already-open session
+ PackageInstaller.Session session = mOpenSessionMap.get(packageName);
+ if (session != null) {
+ try {
+ // Probe the session to ensure that it's still open. This may or may not
+ // throw (if non-open), but it may serve as a canary for stale sessions.
+ session.getNames();
+ return session;
+ } catch (IOException ioe) {
+ Log.e(TAG, "Stale open session for " + packageName + ": " + ioe.getMessage());
+ mOpenSessionMap.remove(packageName);
+ } catch (SecurityException se) {
+ Log.e(TAG, "Stale open session for " + packageName + ": " + se.getMessage());
+ mOpenSessionMap.remove(packageName);
+ }
+ }
+ // Check to see if this is a known session
+ PackageInstaller.SessionInfo sessionInfo = mSessionInfoMap.get(packageName);
+ if (sessionInfo == null) {
+ return null;
+ }
+ // Try to open it. If we fail here, assume that the SessionInfo was stale.
+ try {
+ session = mPackageInstaller.openSession(sessionInfo.getSessionId());
+ } catch (SecurityException se) {
+ Log.w(TAG, "SessionInfo was stale for " + packageName + " - deleting info");
+ mSessionInfoMap.remove(packageName);
+ return null;
+ } catch (IOException ioe) {
+ Log.w(TAG, "IOException opening old session for " + ioe.getMessage()
+ + " - deleting info");
+ mSessionInfoMap.remove(packageName);
+ return null;
+ }
+ mOpenSessionMap.put(packageName, session);
+ return session;
+ }
+
+ /** This version throws an IOException when the session cannot be created */
+ private void innerCreateSession(String packageName) throws IOException {
+ if (mSessionInfoMap.containsKey(packageName)) {
+ Log.w(TAG, "Creating session for " + packageName + " when one already exists");
+ return;
+ }
+ PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+ PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ params.setAppPackageName(packageName);
+
+ // IOException may be thrown at this point
+ int sessionId = mPackageInstaller.createSession(params);
+ PackageInstaller.SessionInfo sessionInfo = mPackageInstaller.getSessionInfo(sessionId);
+ mSessionInfoMap.put(packageName, sessionInfo);
+ }
+
+ /**
+ * Cancel a session based on its sessionId. Package name is for logging only.
+ */
+ private void cancelSession(int sessionId, String packageName) {
+ // Close if currently held open
+ closeSession(packageName);
+ // Remove local record
+ mSessionInfoMap.remove(packageName);
+ try {
+ mPackageInstaller.abandonSession(sessionId);
+ } catch (SecurityException se) {
+ // The session no longer exists, so we can exit quietly.
+ return;
+ }
+ }
+
+ /**
+ * Close a session if it happens to be held open.
+ */
+ private void closeSession(String packageName) {
+ PackageInstaller.Session session = mOpenSessionMap.remove(packageName);
+ if (session != null) {
+ // Unfortunately close() is not idempotent. Try our best to make this safe.
+ try {
+ session.close();
+ } catch (Exception e) {
+ Log.w(TAG, "Unexpected error closing session for " + packageName + ": "
+ + e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Creates a commit callback for the package install that's underway. This will be called
+ * some time after calling session.commit() (above).
+ */
+ private IntentSender getCommitCallback(final String packageName, final int sessionId,
+ final InstallListener callback) {
+ // Create a single-use broadcast receiver
+ BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mContext.unregisterReceiver(this);
+ handleCommitCallback(intent, packageName, sessionId, callback);
+ }
+ };
+ // Create a matching intent-filter and register the receiver
+ String action = ACTION_INSTALL_COMMIT + "." + packageName;
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(action);
+ mContext.registerReceiver(broadcastReceiver, intentFilter,
+ Context.RECEIVER_EXPORTED);
+
+ // Create a matching PendingIntent and use it to generate the IntentSender
+ Intent broadcastIntent = new Intent(action).setPackage(mContext.getPackageName());
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, packageName.hashCode(),
+ broadcastIntent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT
+ | PendingIntent.FLAG_MUTABLE);
+ return pendingIntent.getIntentSender();
+ }
+
+ /**
+ * Examine the extras to determine information about the package update/install, decode
+ * the result, and call the appropriate callback.
+ *
+ * @param intent The intent, which the PackageInstaller will have added Extras to
+ * @param packageName The package name we created the receiver for
+ * @param sessionId The session Id we created the receiver for
+ * @param callback The callback to report success/failure to
+ */
+ private void handleCommitCallback(Intent intent, String packageName, int sessionId,
+ InstallListener callback) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Installation of " + packageName + " finished with extras "
+ + intent.getExtras());
+ }
+ String statusMessage = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
+ int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, Integer.MIN_VALUE);
+ if (status == PackageInstaller.STATUS_SUCCESS) {
+ cancelSession(sessionId, packageName);
+ callback.installSucceeded();
+ } else if (status == -1 /*PackageInstaller.STATUS_USER_ACTION_REQUIRED*/) {
+ // TODO - use the constant when the correct/final name is in the SDK
+ // TODO This is unexpected, so we are treating as failure for now
+ cancelSession(sessionId, packageName);
+ callback.installFailed(InstallerConstants.ERROR_INSTALL_USER_ACTION_REQUIRED,
+ "Unexpected: user action required");
+ } else {
+ cancelSession(sessionId, packageName);
+ int errorCode = getPackageManagerErrorCode(status);
+ Log.e(TAG, "Error " + errorCode + " while installing " + packageName + ": "
+ + statusMessage);
+ callback.installFailed(errorCode, null);
+ }
+ }
+
+ private int getPackageManagerErrorCode(int status) {
+ // This is a hack: because PackageInstaller now reports error codes
+ // with small positive values, we need to remap them into a space
+ // that is more compatible with the existing package manager error codes.
+ // See https://sites.google.com/a/google.com/universal-store/documentation
+ // /android-client/download-error-codes
+ int errorCode;
+ if (status == Integer.MIN_VALUE) {
+ errorCode = InstallerConstants.ERROR_INSTALL_MALFORMED_BROADCAST;
+ } else {
+ errorCode = InstallerConstants.ERROR_PACKAGEINSTALLER_BASE - status;
+ }
+ return errorCode;
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java
new file mode 100644
index 000000000000..2c289b2a6f94
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015 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.packageinstaller.wear;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+
+/**
+ * Installation Util that contains a list of parameters that are needed for
+ * installing/uninstalling.
+ */
+public class WearPackageArgs {
+ private static final String KEY_PACKAGE_NAME =
+ "com.google.android.clockwork.EXTRA_PACKAGE_NAME";
+ private static final String KEY_ASSET_URI =
+ "com.google.android.clockwork.EXTRA_ASSET_URI";
+ private static final String KEY_START_ID =
+ "com.google.android.clockwork.EXTRA_START_ID";
+ private static final String KEY_PERM_URI =
+ "com.google.android.clockwork.EXTRA_PERM_URI";
+ private static final String KEY_CHECK_PERMS =
+ "com.google.android.clockwork.EXTRA_CHECK_PERMS";
+ private static final String KEY_SKIP_IF_SAME_VERSION =
+ "com.google.android.clockwork.EXTRA_SKIP_IF_SAME_VERSION";
+ private static final String KEY_COMPRESSION_ALG =
+ "com.google.android.clockwork.EXTRA_KEY_COMPRESSION_ALG";
+ private static final String KEY_COMPANION_SDK_VERSION =
+ "com.google.android.clockwork.EXTRA_KEY_COMPANION_SDK_VERSION";
+ private static final String KEY_COMPANION_DEVICE_VERSION =
+ "com.google.android.clockwork.EXTRA_KEY_COMPANION_DEVICE_VERSION";
+ private static final String KEY_SHOULD_CHECK_GMS_DEPENDENCY =
+ "com.google.android.clockwork.EXTRA_KEY_SHOULD_CHECK_GMS_DEPENDENCY";
+ private static final String KEY_SKIP_IF_LOWER_VERSION =
+ "com.google.android.clockwork.EXTRA_SKIP_IF_LOWER_VERSION";
+
+ public static String getPackageName(Bundle b) {
+ return b.getString(KEY_PACKAGE_NAME);
+ }
+
+ public static Bundle setPackageName(Bundle b, String packageName) {
+ b.putString(KEY_PACKAGE_NAME, packageName);
+ return b;
+ }
+
+ public static Uri getAssetUri(Bundle b) {
+ return b.getParcelable(KEY_ASSET_URI);
+ }
+
+ public static Uri getPermUri(Bundle b) {
+ return b.getParcelable(KEY_PERM_URI);
+ }
+
+ public static boolean checkPerms(Bundle b) {
+ return b.getBoolean(KEY_CHECK_PERMS);
+ }
+
+ public static boolean skipIfSameVersion(Bundle b) {
+ return b.getBoolean(KEY_SKIP_IF_SAME_VERSION);
+ }
+
+ public static int getCompanionSdkVersion(Bundle b) {
+ return b.getInt(KEY_COMPANION_SDK_VERSION);
+ }
+
+ public static int getCompanionDeviceVersion(Bundle b) {
+ return b.getInt(KEY_COMPANION_DEVICE_VERSION);
+ }
+
+ public static String getCompressionAlg(Bundle b) {
+ return b.getString(KEY_COMPRESSION_ALG);
+ }
+
+ public static int getStartId(Bundle b) {
+ return b.getInt(KEY_START_ID);
+ }
+
+ public static boolean skipIfLowerVersion(Bundle b) {
+ return b.getBoolean(KEY_SKIP_IF_LOWER_VERSION, false);
+ }
+
+ public static Bundle setStartId(Bundle b, int startId) {
+ b.putInt(KEY_START_ID, startId);
+ return b;
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java
new file mode 100644
index 000000000000..02b9d298db0e
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2015 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.packageinstaller.wear;
+
+import android.annotation.TargetApi;
+import android.app.ActivityManager;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.List;
+
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+public class WearPackageIconProvider extends ContentProvider {
+ private static final String TAG = "WearPackageIconProvider";
+ public static final String AUTHORITY = "com.google.android.packageinstaller.wear.provider";
+
+ private static final String REQUIRED_PERMISSION =
+ "com.google.android.permission.INSTALL_WEARABLE_PACKAGES";
+
+ /** MIME types. */
+ public static final String ICON_TYPE = "vnd.android.cursor.item/cw_package_icon";
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ throw new UnsupportedOperationException("Query is not supported.");
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ if (uri == null) {
+ throw new IllegalArgumentException("URI passed in is null.");
+ }
+
+ if (AUTHORITY.equals(uri.getEncodedAuthority())) {
+ return ICON_TYPE;
+ }
+ return null;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ throw new UnsupportedOperationException("Insert is not supported.");
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ if (uri == null) {
+ throw new IllegalArgumentException("URI passed in is null.");
+ }
+
+ enforcePermissions(uri);
+
+ if (ICON_TYPE.equals(getType(uri))) {
+ final File file = WearPackageUtil.getIconFile(
+ this.getContext().getApplicationContext(), getPackageNameFromUri(uri));
+ if (file != null) {
+ file.delete();
+ }
+ }
+
+ return 0;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException("Update is not supported.");
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(
+ Uri uri, @SuppressWarnings("unused") String mode) throws FileNotFoundException {
+ if (uri == null) {
+ throw new IllegalArgumentException("URI passed in is null.");
+ }
+
+ enforcePermissions(uri);
+
+ if (ICON_TYPE.equals(getType(uri))) {
+ final File file = WearPackageUtil.getIconFile(
+ this.getContext().getApplicationContext(), getPackageNameFromUri(uri));
+ if (file != null) {
+ return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
+ }
+ }
+ return null;
+ }
+
+ public static Uri getUriForPackage(final String packageName) {
+ return Uri.parse("content://" + AUTHORITY + "/icons/" + packageName + ".icon");
+ }
+
+ private String getPackageNameFromUri(Uri uri) {
+ if (uri == null) {
+ return null;
+ }
+ List<String> pathSegments = uri.getPathSegments();
+ String packageName = pathSegments.get(pathSegments.size() - 1);
+
+ if (packageName.endsWith(".icon")) {
+ packageName = packageName.substring(0, packageName.lastIndexOf("."));
+ }
+ return packageName;
+ }
+
+ /**
+ * Make sure the calling app is either a system app or the same app or has the right permission.
+ * @throws SecurityException if the caller has insufficient permissions.
+ */
+ @TargetApi(Build.VERSION_CODES.BASE_1_1)
+ private void enforcePermissions(Uri uri) {
+ // Redo some of the permission check in {@link ContentProvider}. Just add an extra check to
+ // allow System process to access this provider.
+ Context context = getContext();
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final int myUid = android.os.Process.myUid();
+
+ if (uid == myUid || isSystemApp(context, pid)) {
+ return;
+ }
+
+ if (context.checkPermission(REQUIRED_PERMISSION, pid, uid) == PERMISSION_GRANTED) {
+ return;
+ }
+
+ // last chance, check against any uri grants
+ if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ == PERMISSION_GRANTED) {
+ return;
+ }
+
+ throw new SecurityException("Permission Denial: reading "
+ + getClass().getName() + " uri " + uri + " from pid=" + pid
+ + ", uid=" + uid);
+ }
+
+ /**
+ * From the pid of the calling process, figure out whether this is a system app or not. We do
+ * this by checking the application information corresponding to the pid and then checking if
+ * FLAG_SYSTEM is set.
+ */
+ @TargetApi(Build.VERSION_CODES.CUPCAKE)
+ private boolean isSystemApp(Context context, int pid) {
+ // Get the Activity Manager Object
+ ActivityManager aManager =
+ (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ // Get the list of running Applications
+ List<ActivityManager.RunningAppProcessInfo> rapInfoList =
+ aManager.getRunningAppProcesses();
+ for (ActivityManager.RunningAppProcessInfo rapInfo : rapInfoList) {
+ if (rapInfo.pid == pid) {
+ try {
+ PackageInfo pkgInfo = context.getPackageManager().getPackageInfo(
+ rapInfo.pkgList[0], 0);
+ if (pkgInfo != null && pkgInfo.applicationInfo != null &&
+ (pkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ Log.d(TAG, pid + " is a system app.");
+ return true;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Could not find package information.", e);
+ return false;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
new file mode 100644
index 000000000000..ae0f4ece1c17
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
@@ -0,0 +1,621 @@
+/*
+ * Copyright (C) 2015 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.packageinstaller.wear;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.FeatureInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
+import android.os.Process;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Pair;
+import androidx.annotation.Nullable;
+import com.android.packageinstaller.DeviceUtils;
+import com.android.packageinstaller.PackageUtil;
+import com.android.packageinstaller.R;
+import com.android.packageinstaller.common.EventResultPersister;
+import com.android.packageinstaller.common.UninstallEventReceiver;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Service that will install/uninstall packages. It will check for permissions and features as well.
+ *
+ * -----------
+ *
+ * Debugging information:
+ *
+ * Install Action example:
+ * adb shell am startservice -a com.android.packageinstaller.wear.INSTALL_PACKAGE \
+ * -d package://com.google.android.gms \
+ * --eu com.google.android.clockwork.EXTRA_ASSET_URI content://com.google.android.clockwork.home.provider/host/com.google.android.wearable.app/wearable/com.google.android.gms/apk \
+ * --es android.intent.extra.INSTALLER_PACKAGE_NAME com.google.android.gms \
+ * --ez com.google.android.clockwork.EXTRA_CHECK_PERMS false \
+ * --eu com.google.android.clockwork.EXTRA_PERM_URI content://com.google.android.clockwork.home.provider/host/com.google.android.wearable.app/permissions \
+ * com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
+ *
+ * Uninstall Action example:
+ * adb shell am startservice -a com.android.packageinstaller.wear.UNINSTALL_PACKAGE \
+ * -d package://com.google.android.gms \
+ * com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
+ *
+ * Retry GMS:
+ * adb shell am startservice -a com.android.packageinstaller.wear.RETRY_GMS \
+ * com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
+ */
+public class WearPackageInstallerService extends Service
+ implements EventResultPersister.EventResultObserver {
+ private static final String TAG = "WearPkgInstallerService";
+
+ private static final String WEAR_APPS_CHANNEL = "wear_app_install_uninstall";
+ private static final String BROADCAST_ACTION =
+ "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT";
+
+ private final int START_INSTALL = 1;
+ private final int START_UNINSTALL = 2;
+
+ private int mInstallNotificationId = 1;
+ private final Map<String, Integer> mNotifIdMap = new ArrayMap<>();
+ private final Map<Integer, UninstallParams> mServiceIdToParams = new HashMap<>();
+
+ private class UninstallParams {
+ public String mPackageName;
+ public PowerManager.WakeLock mLock;
+
+ UninstallParams(String packageName, PowerManager.WakeLock lock) {
+ mPackageName = packageName;
+ mLock = lock;
+ }
+ }
+
+ private final class ServiceHandler extends Handler {
+ public ServiceHandler(Looper looper) {
+ super(looper);
+ }
+
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case START_INSTALL:
+ installPackage(msg.getData());
+ break;
+ case START_UNINSTALL:
+ uninstallPackage(msg.getData());
+ break;
+ }
+ }
+ }
+ private ServiceHandler mServiceHandler;
+ private NotificationChannel mNotificationChannel;
+ private static volatile PowerManager.WakeLock lockStatic = null;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ HandlerThread thread = new HandlerThread("PackageInstallerThread",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ thread.start();
+
+ mServiceHandler = new ServiceHandler(thread.getLooper());
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (!DeviceUtils.isWear(this)) {
+ Log.w(TAG, "Not running on wearable.");
+ finishServiceEarly(startId);
+ return START_NOT_STICKY;
+ }
+
+ if (intent == null) {
+ Log.w(TAG, "Got null intent.");
+ finishServiceEarly(startId);
+ return START_NOT_STICKY;
+ }
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Got install/uninstall request " + intent);
+ }
+
+ Uri packageUri = intent.getData();
+ if (packageUri == null) {
+ Log.e(TAG, "No package URI in intent");
+ finishServiceEarly(startId);
+ return START_NOT_STICKY;
+ }
+
+ final String packageName = WearPackageUtil.getSanitizedPackageName(packageUri);
+ if (packageName == null) {
+ Log.e(TAG, "Invalid package name in URI (expected package:<pkgName>): " + packageUri);
+ finishServiceEarly(startId);
+ return START_NOT_STICKY;
+ }
+
+ PowerManager.WakeLock lock = getLock(this.getApplicationContext());
+ if (!lock.isHeld()) {
+ lock.acquire();
+ }
+
+ Bundle intentBundle = intent.getExtras();
+ if (intentBundle == null) {
+ intentBundle = new Bundle();
+ }
+ WearPackageArgs.setStartId(intentBundle, startId);
+ WearPackageArgs.setPackageName(intentBundle, packageName);
+ Message msg;
+ String notifTitle;
+ if (Intent.ACTION_INSTALL_PACKAGE.equals(intent.getAction())) {
+ msg = mServiceHandler.obtainMessage(START_INSTALL);
+ notifTitle = getString(R.string.installing);
+ } else if (Intent.ACTION_UNINSTALL_PACKAGE.equals(intent.getAction())) {
+ msg = mServiceHandler.obtainMessage(START_UNINSTALL);
+ notifTitle = getString(R.string.uninstalling);
+ } else {
+ Log.e(TAG, "Unknown action : " + intent.getAction());
+ finishServiceEarly(startId);
+ return START_NOT_STICKY;
+ }
+ Pair<Integer, Notification> notifPair = buildNotification(packageName, notifTitle);
+ startForeground(notifPair.first, notifPair.second);
+ msg.setData(intentBundle);
+ mServiceHandler.sendMessage(msg);
+ return START_NOT_STICKY;
+ }
+
+ private void installPackage(Bundle argsBundle) {
+ int startId = WearPackageArgs.getStartId(argsBundle);
+ final String packageName = WearPackageArgs.getPackageName(argsBundle);
+ final Uri assetUri = WearPackageArgs.getAssetUri(argsBundle);
+ final Uri permUri = WearPackageArgs.getPermUri(argsBundle);
+ boolean checkPerms = WearPackageArgs.checkPerms(argsBundle);
+ boolean skipIfSameVersion = WearPackageArgs.skipIfSameVersion(argsBundle);
+ int companionSdkVersion = WearPackageArgs.getCompanionSdkVersion(argsBundle);
+ int companionDeviceVersion = WearPackageArgs.getCompanionDeviceVersion(argsBundle);
+ String compressionAlg = WearPackageArgs.getCompressionAlg(argsBundle);
+ boolean skipIfLowerVersion = WearPackageArgs.skipIfLowerVersion(argsBundle);
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Installing package: " + packageName + ", assetUri: " + assetUri +
+ ",permUri: " + permUri + ", startId: " + startId + ", checkPerms: " +
+ checkPerms + ", skipIfSameVersion: " + skipIfSameVersion +
+ ", compressionAlg: " + compressionAlg + ", companionSdkVersion: " +
+ companionSdkVersion + ", companionDeviceVersion: " + companionDeviceVersion +
+ ", skipIfLowerVersion: " + skipIfLowerVersion);
+ }
+ final PackageManager pm = getPackageManager();
+ File tempFile = null;
+ PowerManager.WakeLock lock = getLock(this.getApplicationContext());
+ boolean messageSent = false;
+ try {
+ PackageInfo existingPkgInfo = null;
+ try {
+ existingPkgInfo = pm.getPackageInfo(packageName,
+ PackageManager.MATCH_ANY_USER | PackageManager.GET_PERMISSIONS);
+ if (existingPkgInfo != null) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Replacing package:" + packageName);
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // Ignore this exception. We could not find the package, will treat as a new
+ // installation.
+ }
+ // TODO(28021618): This was left as a temp file due to the fact that this code is being
+ // deprecated and that we need the bare minimum to continue working moving forward
+ // If this code is used as reference, this permission logic might want to be
+ // reworked to use a stream instead of a file so that we don't need to write a
+ // file at all. Note that there might be some trickiness with opening a stream
+ // for multiple users.
+ ParcelFileDescriptor parcelFd = getContentResolver()
+ .openFileDescriptor(assetUri, "r");
+ tempFile = WearPackageUtil.getFileFromFd(WearPackageInstallerService.this,
+ parcelFd, packageName, compressionAlg);
+ if (tempFile == null) {
+ Log.e(TAG, "Could not create a temp file from FD for " + packageName);
+ return;
+ }
+ PackageInfo pkgInfo = PackageUtil.getPackageInfo(this, tempFile,
+ PackageManager.GET_PERMISSIONS | PackageManager.GET_CONFIGURATIONS);
+ if (pkgInfo == null) {
+ Log.e(TAG, "Could not parse apk information for " + packageName);
+ return;
+ }
+
+ if (!pkgInfo.packageName.equals(packageName)) {
+ Log.e(TAG, "Wearable Package Name has to match what is provided for " +
+ packageName);
+ return;
+ }
+
+ ApplicationInfo appInfo = pkgInfo.applicationInfo;
+ appInfo.sourceDir = tempFile.getPath();
+ appInfo.publicSourceDir = tempFile.getPath();
+ getLabelAndUpdateNotification(packageName,
+ getString(R.string.installing_app, appInfo.loadLabel(pm)));
+
+ List<String> wearablePerms = Arrays.asList(pkgInfo.requestedPermissions);
+
+ // Log if the installed pkg has a higher version number.
+ if (existingPkgInfo != null) {
+ long longVersionCode = pkgInfo.getLongVersionCode();
+ if (existingPkgInfo.getLongVersionCode() == longVersionCode) {
+ if (skipIfSameVersion) {
+ Log.w(TAG, "Version number (" + longVersionCode +
+ ") of new app is equal to existing app for " + packageName +
+ "; not installing due to versionCheck");
+ return;
+ } else {
+ Log.w(TAG, "Version number of new app (" + longVersionCode +
+ ") is equal to existing app for " + packageName);
+ }
+ } else if (existingPkgInfo.getLongVersionCode() > longVersionCode) {
+ if (skipIfLowerVersion) {
+ // Starting in Feldspar, we are not going to allow downgrades of any app.
+ Log.w(TAG, "Version number of new app (" + longVersionCode +
+ ") is lower than existing app ( "
+ + existingPkgInfo.getLongVersionCode() +
+ ") for " + packageName + "; not installing due to versionCheck");
+ return;
+ } else {
+ Log.w(TAG, "Version number of new app (" + longVersionCode +
+ ") is lower than existing app ( "
+ + existingPkgInfo.getLongVersionCode() + ") for " + packageName);
+ }
+ }
+
+ // Following the Android Phone model, we should only check for permissions for any
+ // newly defined perms.
+ if (existingPkgInfo.requestedPermissions != null) {
+ for (int i = 0; i < existingPkgInfo.requestedPermissions.length; ++i) {
+ // If the permission is granted, then we will not ask to request it again.
+ if ((existingPkgInfo.requestedPermissionsFlags[i] &
+ PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, existingPkgInfo.requestedPermissions[i] +
+ " is already granted for " + packageName);
+ }
+ wearablePerms.remove(existingPkgInfo.requestedPermissions[i]);
+ }
+ }
+ }
+ }
+
+ // Check that the wearable has all the features.
+ boolean hasAllFeatures = true;
+ for (FeatureInfo feature : pkgInfo.reqFeatures) {
+ if (feature.name != null && !pm.hasSystemFeature(feature.name) &&
+ (feature.flags & FeatureInfo.FLAG_REQUIRED) != 0) {
+ Log.e(TAG, "Wearable does not have required feature: " + feature +
+ " for " + packageName);
+ hasAllFeatures = false;
+ }
+ }
+
+ if (!hasAllFeatures) {
+ return;
+ }
+
+ // Check permissions on both the new wearable package and also on the already installed
+ // wearable package.
+ // If the app is targeting API level 23, we will also start a service in ClockworkHome
+ // which will ultimately prompt the user to accept/reject permissions.
+ if (checkPerms && !checkPermissions(pkgInfo, companionSdkVersion,
+ companionDeviceVersion, permUri, wearablePerms, tempFile)) {
+ Log.w(TAG, "Wearable does not have enough permissions.");
+ return;
+ }
+
+ // Finally install the package.
+ ParcelFileDescriptor fd = getContentResolver().openFileDescriptor(assetUri, "r");
+ PackageInstallerFactory.getPackageInstaller(this).install(packageName, fd,
+ new PackageInstallListener(this, lock, startId, packageName));
+
+ messageSent = true;
+ Log.i(TAG, "Sent installation request for " + packageName);
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Could not find the file with URI " + assetUri, e);
+ } finally {
+ if (!messageSent) {
+ // Some error happened. If the message has been sent, we can wait for the observer
+ // which will finish the service.
+ if (tempFile != null) {
+ tempFile.delete();
+ }
+ finishService(lock, startId);
+ }
+ }
+ }
+
+ // TODO: This was left using the old PackageManager API due to the fact that this code is being
+ // deprecated and that we need the bare minimum to continue working moving forward
+ // If this code is used as reference, this logic should be reworked to use the new
+ // PackageInstaller APIs similar to how installPackage was reworked
+ private void uninstallPackage(Bundle argsBundle) {
+ int startId = WearPackageArgs.getStartId(argsBundle);
+ final String packageName = WearPackageArgs.getPackageName(argsBundle);
+
+ PowerManager.WakeLock lock = getLock(this.getApplicationContext());
+
+ UninstallParams params = new UninstallParams(packageName, lock);
+ mServiceIdToParams.put(startId, params);
+
+ final PackageManager pm = getPackageManager();
+ try {
+ PackageInfo pkgInfo = pm.getPackageInfo(packageName, 0);
+ getLabelAndUpdateNotification(packageName,
+ getString(R.string.uninstalling_app, pkgInfo.applicationInfo.loadLabel(pm)));
+
+ int uninstallId = UninstallEventReceiver.addObserver(this,
+ EventResultPersister.GENERATE_NEW_ID, this);
+
+ Intent broadcastIntent = new Intent(BROADCAST_ACTION);
+ broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, uninstallId);
+ broadcastIntent.putExtra(EventResultPersister.EXTRA_SERVICE_ID, startId);
+ broadcastIntent.setPackage(getPackageName());
+
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(this, uninstallId,
+ broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT
+ | PendingIntent.FLAG_MUTABLE);
+
+ // Found package, send uninstall request.
+ pm.getPackageInstaller().uninstall(
+ new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
+ PackageManager.DELETE_ALL_USERS,
+ pendingIntent.getIntentSender());
+
+ Log.i(TAG, "Sent delete request for " + packageName);
+ } catch (IllegalArgumentException | PackageManager.NameNotFoundException e) {
+ // Couldn't find the package, no need to call uninstall.
+ Log.w(TAG, "Could not find package, not deleting " + packageName, e);
+ finishService(lock, startId);
+ } catch (EventResultPersister.OutOfIdsException e) {
+ Log.e(TAG, "Fails to start uninstall", e);
+ finishService(lock, startId);
+ }
+ }
+
+ @Override
+ public void onResult(int status, int legacyStatus, @Nullable String message, int serviceId) {
+ if (mServiceIdToParams.containsKey(serviceId)) {
+ UninstallParams params = mServiceIdToParams.get(serviceId);
+ try {
+ if (status == PackageInstaller.STATUS_SUCCESS) {
+ Log.i(TAG, "Package " + params.mPackageName + " was uninstalled.");
+ } else {
+ Log.e(TAG, "Package uninstall failed " + params.mPackageName
+ + ", returnCode " + legacyStatus);
+ }
+ } finally {
+ finishService(params.mLock, serviceId);
+ }
+ }
+ }
+
+ private boolean checkPermissions(PackageInfo pkgInfo, int companionSdkVersion,
+ int companionDeviceVersion, Uri permUri, List<String> wearablePermissions,
+ File apkFile) {
+ // Assumption: We are running on Android O.
+ // If the Phone App is targeting M, all permissions may not have been granted to the phone
+ // app. If the Wear App is then not targeting M, there may be permissions that are not
+ // granted on the Phone app (by the user) right now and we cannot just grant it for the Wear
+ // app.
+ if (pkgInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M) {
+ // Install the app if Wear App is ready for the new perms model.
+ return true;
+ }
+
+ if (!doesWearHaveUngrantedPerms(pkgInfo.packageName, permUri, wearablePermissions)) {
+ // All permissions requested by the watch are already granted on the phone, no need
+ // to do anything.
+ return true;
+ }
+
+ // Log an error if Wear is targeting < 23 and phone is targeting >= 23.
+ if (companionSdkVersion == 0 || companionSdkVersion >= Build.VERSION_CODES.M) {
+ Log.e(TAG, "MNC: Wear app's targetSdkVersion should be at least 23, if "
+ + "phone app is targeting at least 23, will continue.");
+ }
+
+ return false;
+ }
+
+ /**
+ * Given a {@string packageName} corresponding to a phone app, query the provider for all the
+ * perms that are granted.
+ *
+ * @return true if the Wear App has any perms that have not been granted yet on the phone side.
+ * @return true if there is any error cases.
+ */
+ private boolean doesWearHaveUngrantedPerms(String packageName, Uri permUri,
+ List<String> wearablePermissions) {
+ if (permUri == null) {
+ Log.e(TAG, "Permission URI is null");
+ // Pretend there is an ungranted permission to avoid installing for error cases.
+ return true;
+ }
+ Cursor permCursor = getContentResolver().query(permUri, null, null, null, null);
+ if (permCursor == null) {
+ Log.e(TAG, "Could not get the cursor for the permissions");
+ // Pretend there is an ungranted permission to avoid installing for error cases.
+ return true;
+ }
+
+ Set<String> grantedPerms = new HashSet<>();
+ Set<String> ungrantedPerms = new HashSet<>();
+ while(permCursor.moveToNext()) {
+ // Make sure that the MatrixCursor returned by the ContentProvider has 2 columns and
+ // verify their types.
+ if (permCursor.getColumnCount() == 2
+ && Cursor.FIELD_TYPE_STRING == permCursor.getType(0)
+ && Cursor.FIELD_TYPE_INTEGER == permCursor.getType(1)) {
+ String perm = permCursor.getString(0);
+ Integer granted = permCursor.getInt(1);
+ if (granted == 1) {
+ grantedPerms.add(perm);
+ } else {
+ ungrantedPerms.add(perm);
+ }
+ }
+ }
+ permCursor.close();
+
+ boolean hasUngrantedPerm = false;
+ for (String wearablePerm : wearablePermissions) {
+ if (!grantedPerms.contains(wearablePerm)) {
+ hasUngrantedPerm = true;
+ if (!ungrantedPerms.contains(wearablePerm)) {
+ // This is an error condition. This means that the wearable has permissions that
+ // are not even declared in its host app. This is a developer error.
+ Log.e(TAG, "Wearable " + packageName + " has a permission \"" + wearablePerm
+ + "\" that is not defined in the host application's manifest.");
+ } else {
+ Log.w(TAG, "Wearable " + packageName + " has a permission \"" + wearablePerm +
+ "\" that is not granted in the host application.");
+ }
+ }
+ }
+ return hasUngrantedPerm;
+ }
+
+ /** Finishes the service after fulfilling obligation to call startForeground. */
+ private void finishServiceEarly(int startId) {
+ Pair<Integer, Notification> notifPair = buildNotification(
+ getApplicationContext().getPackageName(), "");
+ startForeground(notifPair.first, notifPair.second);
+ finishService(null, startId);
+ }
+
+ private void finishService(PowerManager.WakeLock lock, int startId) {
+ if (lock != null && lock.isHeld()) {
+ lock.release();
+ }
+ stopSelf(startId);
+ }
+
+ private synchronized PowerManager.WakeLock getLock(Context context) {
+ if (lockStatic == null) {
+ PowerManager mgr =
+ (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ lockStatic = mgr.newWakeLock(
+ PowerManager.PARTIAL_WAKE_LOCK, context.getClass().getSimpleName());
+ lockStatic.setReferenceCounted(true);
+ }
+ return lockStatic;
+ }
+
+ private class PackageInstallListener implements PackageInstallerImpl.InstallListener {
+ private Context mContext;
+ private PowerManager.WakeLock mWakeLock;
+ private int mStartId;
+ private String mApplicationPackageName;
+ private PackageInstallListener(Context context, PowerManager.WakeLock wakeLock,
+ int startId, String applicationPackageName) {
+ mContext = context;
+ mWakeLock = wakeLock;
+ mStartId = startId;
+ mApplicationPackageName = applicationPackageName;
+ }
+
+ @Override
+ public void installBeginning() {
+ Log.i(TAG, "Package " + mApplicationPackageName + " is being installed.");
+ }
+
+ @Override
+ public void installSucceeded() {
+ try {
+ Log.i(TAG, "Package " + mApplicationPackageName + " was installed.");
+
+ // Delete tempFile from the file system.
+ File tempFile = WearPackageUtil.getTemporaryFile(mContext, mApplicationPackageName);
+ if (tempFile != null) {
+ tempFile.delete();
+ }
+ } finally {
+ finishService(mWakeLock, mStartId);
+ }
+ }
+
+ @Override
+ public void installFailed(int errorCode, String errorDesc) {
+ Log.e(TAG, "Package install failed " + mApplicationPackageName
+ + ", errorCode " + errorCode);
+ finishService(mWakeLock, mStartId);
+ }
+ }
+
+ private synchronized Pair<Integer, Notification> buildNotification(final String packageName,
+ final String title) {
+ int notifId;
+ if (mNotifIdMap.containsKey(packageName)) {
+ notifId = mNotifIdMap.get(packageName);
+ } else {
+ notifId = mInstallNotificationId++;
+ mNotifIdMap.put(packageName, notifId);
+ }
+
+ if (mNotificationChannel == null) {
+ mNotificationChannel = new NotificationChannel(WEAR_APPS_CHANNEL,
+ getString(R.string.wear_app_channel), NotificationManager.IMPORTANCE_MIN);
+ NotificationManager notificationManager = getSystemService(NotificationManager.class);
+ notificationManager.createNotificationChannel(mNotificationChannel);
+ }
+ return new Pair<>(notifId, new Notification.Builder(this, WEAR_APPS_CHANNEL)
+ .setSmallIcon(R.drawable.ic_file_download)
+ .setContentTitle(title)
+ .build());
+ }
+
+ private void getLabelAndUpdateNotification(String packageName, String title) {
+ // Update notification since we have a label now.
+ NotificationManager notificationManager = getSystemService(NotificationManager.class);
+ Pair<Integer, Notification> notifPair = buildNotification(packageName, title);
+ notificationManager.notify(notifPair.first, notifPair.second);
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java
new file mode 100644
index 000000000000..6a9145db9a06
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2015 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.packageinstaller.wear;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.text.TextUtils;
+import android.util.Log;
+
+import org.tukaani.xz.LZMAInputStream;
+import org.tukaani.xz.XZInputStream;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class WearPackageUtil {
+ private static final String TAG = "WearablePkgInstaller";
+
+ private static final String COMPRESSION_LZMA = "lzma";
+ private static final String COMPRESSION_XZ = "xz";
+
+ public static File getTemporaryFile(Context context, String packageName) {
+ try {
+ File newFileDir = new File(context.getFilesDir(), "tmp");
+ newFileDir.mkdirs();
+ Os.chmod(newFileDir.getAbsolutePath(), 0771);
+ File newFile = new File(newFileDir, packageName + ".apk");
+ return newFile;
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to open.", e);
+ return null;
+ }
+ }
+
+ public static File getIconFile(final Context context, final String packageName) {
+ try {
+ File newFileDir = new File(context.getFilesDir(), "images/icons");
+ newFileDir.mkdirs();
+ Os.chmod(newFileDir.getAbsolutePath(), 0771);
+ return new File(newFileDir, packageName + ".icon");
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to open.", e);
+ return null;
+ }
+ }
+
+ /**
+ * In order to make sure that the Wearable Asset Manager has a reasonable apk that can be used
+ * by the PackageManager, we will parse it before sending it to the PackageManager.
+ * Unfortunately, ParsingPackageUtils needs a file to parse. So, we have to temporarily convert
+ * the fd to a File.
+ *
+ * @param context
+ * @param fd FileDescriptor to convert to File
+ * @param packageName Name of package, will define the name of the file
+ * @param compressionAlg Can be null. For ALT mode the APK will be compressed. We will
+ * decompress it here
+ */
+ public static File getFileFromFd(Context context, ParcelFileDescriptor fd,
+ String packageName, String compressionAlg) {
+ File newFile = getTemporaryFile(context, packageName);
+ if (fd == null || fd.getFileDescriptor() == null) {
+ return null;
+ }
+ InputStream fr = new ParcelFileDescriptor.AutoCloseInputStream(fd);
+ try {
+ if (TextUtils.equals(compressionAlg, COMPRESSION_XZ)) {
+ fr = new XZInputStream(fr);
+ } else if (TextUtils.equals(compressionAlg, COMPRESSION_LZMA)) {
+ fr = new LZMAInputStream(fr);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Compression was set to " + compressionAlg + ", but could not decode ", e);
+ return null;
+ }
+
+ int nRead;
+ byte[] data = new byte[1024];
+ try {
+ final FileOutputStream fo = new FileOutputStream(newFile);
+ while ((nRead = fr.read(data, 0, data.length)) != -1) {
+ fo.write(data, 0, nRead);
+ }
+ fo.flush();
+ fo.close();
+ Os.chmod(newFile.getAbsolutePath(), 0644);
+ return newFile;
+ } catch (IOException e) {
+ Log.e(TAG, "Reading from Asset FD or writing to temp file failed ", e);
+ return null;
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Could not set permissions on file ", e);
+ return null;
+ } finally {
+ try {
+ fr.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to close the file from FD ", e);
+ }
+ }
+ }
+
+ /**
+ * @return com.google.com from expected formats like
+ * Uri: package:com.google.com, package:/com.google.com, package://com.google.com
+ */
+ public static String getSanitizedPackageName(Uri packageUri) {
+ String packageName = packageUri.getEncodedSchemeSpecificPart();
+ if (packageName != null) {
+ return packageName.replaceAll("^/+", "");
+ }
+ return packageName;
+ }
+}
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v35/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v35/themes.xml
new file mode 100644
index 000000000000..fadcf7ba8699
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v35/themes.xml
@@ -0,0 +1,24 @@
+<?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>
+ <style name="Theme.CollapsingToolbar.Settings" parent="@style/Theme.MaterialComponents.DayNight">
+ <item name="elevationOverlayEnabled">true</item>
+ <item name="elevationOverlayColor">?attr/colorPrimary</item>
+ <item name="colorPrimary">@color/settingslib_materialColorOnSurfaceInverse</item>
+ <item name="colorAccent">@color/settingslib_materialColorPrimaryFixed</item>
+ </style>
+</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/styles.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/styles.xml
new file mode 100644
index 000000000000..0c2028744bf7
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/styles.xml
@@ -0,0 +1,28 @@
+<?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>
+ <style name="CollapsingToolbarTitle.Collapsed" parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
+ <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
+ <item name="android:textSize">20dp</item>
+ <item name="android:textColor">@color/settingslib_materialColorOnSurface</item>
+ </style>
+
+ <style name="CollapsingToolbarTitle.Expanded" parent="CollapsingToolbarTitle.Collapsed">
+ <item name="android:textSize">36dp</item>
+ <item name="android:textColor">@color/settingslib_materialColorOnSurface</item>
+ </style>
+</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes.xml
new file mode 100644
index 000000000000..7c9d1a47b7ef
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes.xml
@@ -0,0 +1,24 @@
+<?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>
+ <style name="Theme.CollapsingToolbar.Settings" parent="@style/Theme.MaterialComponents.DayNight">
+ <item name="elevationOverlayEnabled">true</item>
+ <item name="elevationOverlayColor">?attr/colorPrimary</item>
+ <item name="colorPrimary">@color/settingslib_materialColorOnSurfaceInverse</item>
+ <item name="colorAccent">@color/settingslib_materialColorPrimary</item>
+ </style>
+</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/FooterPreference/res/layout-v35/preference_footer.xml b/packages/SettingsLib/FooterPreference/res/layout-v35/preference_footer.xml
deleted file mode 100644
index a2b964882c7c..000000000000
--- a/packages/SettingsLib/FooterPreference/res/layout-v35/preference_footer.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-<?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.
- -->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="?android:attr/listPreferredItemHeight"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:background="?android:attr/selectableItemBackground"
- android:orientation="vertical"
- android:clipToPadding="false">
-
- <LinearLayout
- android:id="@+id/icon_frame"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:minWidth="56dp"
- android:gravity="start|top"
- android:orientation="horizontal"
- android:paddingEnd="12dp"
- android:paddingTop="16dp"
- android:paddingBottom="4dp">
- <ImageView
- android:id="@android:id/icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical">
- <TextView
- android:id="@android:id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="start"
- android:textAlignment="viewStart"
- android:paddingTop="16dp"
- android:paddingBottom="8dp"
- android:textColor="@color/settingslib_materialColorOnSurfaceVariant"
- android:hyphenationFrequency="normalFast"
- android:lineBreakWordStyle="phrase"
- android:ellipsize="marquee" />
-
- <com.android.settingslib.widget.LinkTextView
- android:id="@+id/settingslib_learn_more"
- android:text="@string/settingslib_learn_more_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="start"
- android:textAlignment="viewStart"
- android:paddingBottom="8dp"
- android:clickable="true"
- android:visibility="gone" />
- </LinearLayout>
-
-</LinearLayout> \ No newline at end of file
diff --git a/packages/SettingsLib/Graph/Android.bp b/packages/SettingsLib/Graph/Android.bp
new file mode 100644
index 000000000000..e2ed1e448207
--- /dev/null
+++ b/packages/SettingsLib/Graph/Android.bp
@@ -0,0 +1,21 @@
+package {
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+ name: "SettingsLibGraph-srcs",
+ srcs: ["src/**/*"],
+}
+
+android_library {
+ name: "SettingsLibGraph",
+ defaults: [
+ "SettingsLintDefaults",
+ ],
+ srcs: [":SettingsLibGraph-srcs"],
+ static_libs: [
+ "androidx.annotation_annotation",
+ "androidx.preference_preference",
+ ],
+ kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/packages/SettingsLib/Graph/AndroidManifest.xml b/packages/SettingsLib/Graph/AndroidManifest.xml
new file mode 100644
index 000000000000..93acb3589f61
--- /dev/null
+++ b/packages/SettingsLib/Graph/AndroidManifest.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.settingslib.graph">
+
+ <uses-sdk android:minSdkVersion="21" />
+</manifest>
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenManager.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenManager.kt
new file mode 100644
index 000000000000..9231f40e2e78
--- /dev/null
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenManager.kt
@@ -0,0 +1,70 @@
+package com.android.settingslib.graph
+
+import androidx.annotation.StringRes
+import androidx.annotation.XmlRes
+import androidx.preference.Preference
+import androidx.preference.PreferenceManager
+import androidx.preference.PreferenceScreen
+
+/** Manager to create and initialize preference screen. */
+class PreferenceScreenManager(private val preferenceManager: PreferenceManager) {
+ private val context = preferenceManager.context
+ // the map will preserve order
+ private val updaters = mutableMapOf<String, PreferenceUpdater>()
+ private val screenUpdaters = mutableListOf<PreferenceScreenUpdater>()
+
+ /** Creates an empty [PreferenceScreen]. */
+ fun createPreferenceScreen(): PreferenceScreen =
+ preferenceManager.createPreferenceScreen(context)
+
+ /** Creates [PreferenceScreen] from resource. */
+ fun createPreferenceScreen(@XmlRes xmlRes: Int): PreferenceScreen =
+ preferenceManager.inflateFromResource(context, xmlRes, null)
+
+ /** Adds updater for given preference. */
+ fun addPreferenceUpdater(@StringRes key: Int, updater: PreferenceUpdater) =
+ addPreferenceUpdater(context.getString(key), updater)
+
+ /** Adds updater for given preference. */
+ fun addPreferenceUpdater(
+ key: String,
+ updater: PreferenceUpdater,
+ ): PreferenceScreenManager {
+ updaters.put(key, updater)?.let { if (it != updater) throw IllegalArgumentException() }
+ return this
+ }
+
+ /** Adds updater for preference screen. */
+ fun addPreferenceScreenUpdater(updater: PreferenceScreenUpdater): PreferenceScreenManager {
+ screenUpdaters.add(updater)
+ return this
+ }
+
+ /** Adds a list of updaters for preference screen. */
+ fun addPreferenceScreenUpdater(
+ vararg updaters: PreferenceScreenUpdater,
+ ): PreferenceScreenManager {
+ screenUpdaters.addAll(updaters)
+ return this
+ }
+
+ /** Updates preference screen with registered updaters. */
+ fun updatePreferenceScreen(preferenceScreen: PreferenceScreen) {
+ for ((key, updater) in updaters) {
+ preferenceScreen.findPreference<Preference>(key)?.let { updater.updatePreference(it) }
+ }
+ for (updater in screenUpdaters) {
+ updater.updatePreferenceScreen(preferenceScreen)
+ }
+ }
+}
+
+/** Updater of [Preference]. */
+interface PreferenceUpdater {
+ fun updatePreference(preference: Preference)
+}
+
+/** Updater of [PreferenceScreen]. */
+interface PreferenceScreenUpdater {
+ fun updatePreferenceScreen(preferenceScreen: PreferenceScreen)
+}
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenProvider.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenProvider.kt
new file mode 100644
index 000000000000..9e4c1f60851a
--- /dev/null
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceScreenProvider.kt
@@ -0,0 +1,26 @@
+package com.android.settingslib.graph
+
+import android.content.Context
+import androidx.preference.PreferenceScreen
+
+/**
+ * Interface to provide [PreferenceScreen].
+ *
+ * It is expected to be implemented by Activity/Fragment and the implementation needs to use
+ * [Context] APIs (e.g. `getContext()`, `getActivity()`) with caution: preference screen creation
+ * could happen in background service, where the Activity/Fragment lifecycle callbacks (`onCreate`,
+ * `onDestroy`, etc.) are not invoked.
+ */
+interface PreferenceScreenProvider {
+
+ /**
+ * Creates [PreferenceScreen].
+ *
+ * Preference screen creation could happen in background service. The implementation MUST use
+ * given [context] instead of APIs like `getContext()`, `getActivity()`, etc.
+ */
+ fun createPreferenceScreen(
+ context: Context,
+ preferenceScreenManager: PreferenceScreenManager,
+ ): PreferenceScreen?
+}
diff --git a/packages/SettingsLib/MainSwitchPreference/res/color-night-v35/settingslib_main_switch_text_color.xml b/packages/SettingsLib/MainSwitchPreference/res/color-night-v35/settingslib_main_switch_text_color.xml
new file mode 100644
index 000000000000..ea15a67e93cd
--- /dev/null
+++ b/packages/SettingsLib/MainSwitchPreference/res/color-night-v35/settingslib_main_switch_text_color.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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:color="@color/settingslib_materialColorOnPrimaryContainer"
+ android:alpha="?android:attr/disabledAlpha" />
+ <item android:color="@color/settingslib_materialColorOnPrimaryContainer"/>
+</selector> \ No newline at end of file
diff --git a/packages/SettingsLib/MainSwitchPreference/res/color-v35/settingslib_main_switch_text_color.xml b/packages/SettingsLib/MainSwitchPreference/res/color-v35/settingslib_main_switch_text_color.xml
new file mode 100644
index 000000000000..ea15a67e93cd
--- /dev/null
+++ b/packages/SettingsLib/MainSwitchPreference/res/color-v35/settingslib_main_switch_text_color.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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:color="@color/settingslib_materialColorOnPrimaryContainer"
+ android:alpha="?android:attr/disabledAlpha" />
+ <item android:color="@color/settingslib_materialColorOnPrimaryContainer"/>
+</selector> \ No newline at end of file
diff --git a/packages/SettingsLib/ProfileSelector/res/color-night-v35/settingslib_tabs_indicator_color.xml b/packages/SettingsLib/ProfileSelector/res/color-night-v35/settingslib_tabs_indicator_color.xml
new file mode 100644
index 000000000000..5192a9a53572
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/color-night-v35/settingslib_tabs_indicator_color.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@color/settingslib_materialColorSecondaryFixed" />
+</selector> \ No newline at end of file
diff --git a/packages/SettingsLib/ProfileSelector/res/color-v35/settingslib_tabs_indicator_color.xml b/packages/SettingsLib/ProfileSelector/res/color-v35/settingslib_tabs_indicator_color.xml
new file mode 100644
index 000000000000..4b1683294a9a
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/color-v35/settingslib_tabs_indicator_color.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@color/settingslib_materialColorPrimaryFixed" />
+</selector> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-night-v31/settingslib_switch_track_on.xml b/packages/SettingsLib/SettingsTheme/res/color-night-v31/settingslib_switch_track_on.xml
index 81ddf29ac9df..1429e3bdc666 100644
--- a/packages/SettingsLib/SettingsTheme/res/color-night-v31/settingslib_switch_track_on.xml
+++ b/packages/SettingsLib/SettingsTheme/res/color-night-v31/settingslib_switch_track_on.xml
@@ -14,7 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
+<!--Deprecated. After sdk 35 don't use it.-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@android:color/system_accent2_500" android:lStar="51" />
</selector> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-night-v35/settingslib_switch_track_outline_color.xml b/packages/SettingsLib/SettingsTheme/res/color-night-v35/settingslib_switch_track_outline_color.xml
new file mode 100644
index 000000000000..eedc364ff54b
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-night-v35/settingslib_switch_track_outline_color.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Disabled status of thumb -->
+ <item android:state_enabled="false"
+ android:color="@color/settingslib_materialColorOutline"
+ android:alpha="?android:attr/disabledAlpha" />
+ <!-- Toggle off status of thumb -->
+ <item android:state_checked="false"
+ android:color="@color/settingslib_materialColorOutline" />
+ <!-- Enabled or toggle on status of thumb -->
+ <item android:color="@color/settingslib_track_on_color" />
+</selector> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml
index 037b80abc6f9..b46181e20eaa 100644
--- a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml
+++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml
@@ -13,6 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
+<!--Deprecated. After sdk 35, don't use it, using materialColorOnSurfaceInverse in light theme -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@android:color/system_neutral1_500" android:lStar="98" />
</selector> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_switch_track_off.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_switch_track_off.xml
index 762bb31243a2..f0bcf0ab2abe 100644
--- a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_switch_track_off.xml
+++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_switch_track_off.xml
@@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
+<!--Deprecated. After sdk 35 don't use it.-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@android:color/system_neutral2_500" android:lStar="45" />
</selector> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml
new file mode 100644
index 000000000000..4ced9f2469ab
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true" android:color="@color/settingslib_materialColorSecondaryContainer"/>
+ <item android:state_selected="true" android:color="@color/settingslib_materialColorSecondaryContainer"/>
+ <item android:state_activated="true" android:color="@color/settingslib_materialColorSecondaryContainer"/>
+ <item android:color="@color/settingslib_materialColorSurfaceContainerHighest"/> <!-- not selected -->
+</selector> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_switch_track_outline_color.xml b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_switch_track_outline_color.xml
new file mode 100644
index 000000000000..eedc364ff54b
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_switch_track_outline_color.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Disabled status of thumb -->
+ <item android:state_enabled="false"
+ android:color="@color/settingslib_materialColorOutline"
+ android:alpha="?android:attr/disabledAlpha" />
+ <!-- Toggle off status of thumb -->
+ <item android:state_checked="false"
+ android:color="@color/settingslib_materialColorOutline" />
+ <!-- Enabled or toggle on status of thumb -->
+ <item android:color="@color/settingslib_track_on_color" />
+</selector> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_primary.xml b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_primary.xml
new file mode 100644
index 000000000000..230eb7d30aea
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_primary.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="?android:attr/disabledAlpha"
+ android:color="@color/settingslib_materialColorOnSurface"/>
+ <item android:color="@color/settingslib_materialColorOnSurface"/>
+</selector>
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_secondary.xml b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_secondary.xml
new file mode 100644
index 000000000000..5bd2a29ccf9a
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_text_color_secondary.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="?android:attr/disabledAlpha"
+ android:color="@color/settingslib_materialColorOnSurfaceVariant"/>
+ <item android:color="@color/settingslib_materialColorOnSurfaceVariant"/>
+</selector> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_progress_horizontal.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_progress_horizontal.xml
new file mode 100644
index 000000000000..3cb34354de17
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_progress_horizontal.xml
@@ -0,0 +1,38 @@
+<?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.
+-->
+
+<layer-list
+ xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item
+ android:id="@android:id/background">
+ <shape>
+ <corners android:radius="8dp" />
+ <solid android:color="@color/settingslib_materialColorSurfaceVariant" />
+ </shape>
+ </item>
+
+ <item
+ android:id="@android:id/progress">
+ <scale android:scaleWidth="100%" android:useIntrinsicSizeAsMinimum="true">
+ <shape>
+ <corners android:radius="8dp" />
+ <solid android:color="?android:attr/textColorPrimary" />
+ <size android:width="8dp"/>
+ </shape>
+ </scale>
+ </item>
+</layer-list>
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml
new file mode 100644
index 000000000000..285ab7301162
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml
@@ -0,0 +1,30 @@
+<?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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:left="?android:attr/listPreferredItemPaddingStart"
+ android:right="?android:attr/listPreferredItemPaddingEnd"
+ android:top="1dp">
+ <shape android:shape="rectangle">
+ <solid
+ android:color="@color/settingslib_preference_bg_color" />
+ <corners
+ android:radius="?android:attr/dialogCornerRadius" />
+ </shape>
+ </item>
+</layer-list> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml
new file mode 100644
index 000000000000..e417307edc3d
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml
@@ -0,0 +1,33 @@
+<?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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:left="?android:attr/listPreferredItemPaddingStart"
+ android:right="?android:attr/listPreferredItemPaddingEnd"
+ android:top="1dp">
+ <shape android:shape="rectangle">
+ <solid
+ android:color="@color/settingslib_preference_bg_color" />
+ <corners
+ android:topLeftRadius="0dp"
+ android:bottomLeftRadius="?android:attr/dialogCornerRadius"
+ android:topRightRadius="0dp"
+ android:bottomRightRadius="?android:attr/dialogCornerRadius" />
+ </shape>
+ </item>
+</layer-list> \ No newline at end of file
diff --git a/packages/SettingsLib/FooterPreference/res/drawable-v35/settingslib_ic_info_outline_24.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml
index c7fbb5f0374b..e9646575663d 100644
--- a/packages/SettingsLib/FooterPreference/res/drawable-v35/settingslib_ic_info_outline_24.xml
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml
@@ -15,12 +15,16 @@
limitations under the License.
-->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="@color/settingslib_materialColorOnSurfaceVariant"
- android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
-</vector>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:left="?android:attr/listPreferredItemPaddingStart"
+ android:right="?android:attr/listPreferredItemPaddingEnd"
+ android:top="1dp">
+ <shape android:shape="rectangle">
+ <solid
+ android:color="@color/settingslib_preference_bg_color" />
+ <corners
+ android:radius="1dp" />
+ </shape>
+ </item>
+</layer-list> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml
new file mode 100644
index 000000000000..a9d69c264a2c
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml
@@ -0,0 +1,33 @@
+<?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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:left="?android:attr/listPreferredItemPaddingStart"
+ android:right="?android:attr/listPreferredItemPaddingEnd"
+ android:top="1dp">
+ <shape android:shape="rectangle">
+ <solid
+ android:color="@color/settingslib_preference_bg_color" />
+ <corners
+ android:topLeftRadius="?android:attr/dialogCornerRadius"
+ android:bottomLeftRadius="0dp"
+ android:topRightRadius="?android:attr/dialogCornerRadius"
+ android:bottomRightRadius="0dp" />
+ </shape>
+ </item>
+</layer-list> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
index 5411591d8df6..0a36a4fa035f 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
@@ -29,17 +29,20 @@
<color name="settingslib_track_off_color">@android:color/system_neutral1_700</color>
<!-- Dialog accent color -->
+ <!--Deprecated. After sdk 35 don't use it, using materialColorPrimary-->
<color name="settingslib_dialog_accent">@android:color/system_accent1_100</color>
<!-- Dialog background color. -->
<color name="settingslib_dialog_background">@color/settingslib_surface_dark</color>
<!-- Dialog error color. -->
<color name="settingslib_dialog_colorError">#f28b82</color> <!-- Red 300 -->
+ <!--Deprecated. After sdk 35 don't use it.-->
<color name="settingslib_colorSurfaceVariant">@android:color/system_neutral1_700</color>
<color name="settingslib_colorSurfaceHeader">@android:color/system_neutral1_700</color>
<!-- copy from accent_primary_variant_dark_device_default-->
+ <!-- TODO: deprecate it after moving into partner code-->
<color name="settingslib_accent_primary_variant">@android:color/system_accent1_300</color>
<color name="settingslib_text_color_primary_device_default">@android:color/system_neutral1_50</color>
@@ -48,7 +51,9 @@
<color name="settingslib_text_color_preference_category_title">@android:color/system_accent1_100</color>
+ <!--Deprecated. After sdk 35, don't use it, using materialColorOnSurfaceInverse in dark theme -->
<color name="settingslib_surface_dark">@android:color/system_neutral1_800</color>
+ <!--Deprecated. After sdk 35, don't use it-->
<color name="settingslib_colorSurface">@color/settingslib_surface_dark</color>
</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml
index beed90efb508..8cfe54f44fe5 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml
@@ -38,7 +38,8 @@
<color name="settingslib_track_off_color">@android:color/system_surface_container_highest_dark
</color>
+ <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurface-->
<color name="settingslib_text_color_primary_device_default">@android:color/system_on_surface_dark</color>
-
+ <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurfaceVariant-->
<color name="settingslib_text_color_secondary_device_default">@android:color/system_on_surface_variant_dark</color>
</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
index 229d9e330882..7c76ea1ad3b7 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
@@ -16,6 +16,34 @@
-->
<resources>
+ <!-- Material next state on color-->
+ <color name="settingslib_state_on_color">@color/settingslib_materialColorPrimaryContainer</color>
+
+ <!-- Material next state off color-->
+ <color name="settingslib_state_off_color">@color/settingslib_materialColorPrimaryContainer</color>
+
+ <!-- Material next thumb disable color-->
+ <color name="settingslib_thumb_disabled_color">@color/settingslib_materialColorOutline</color>
+
+ <!-- Material next thumb off color-->
+ <color name="settingslib_thumb_on_color">@color/settingslib_materialColorOnPrimary</color>
+
+ <!-- Material next thumb off color-->
+ <color name="settingslib_thumb_off_color">@color/settingslib_materialColorOutline</color>
+
+ <!-- Material next track on color-->
+ <color name="settingslib_track_on_color">@color/settingslib_materialColorPrimary</color>
+
+ <!-- Material next track off color-->
+ <color name="settingslib_track_off_color">@color/settingslib_materialColorSurfaceContainerHighest</color>
+
+ <!-- Dialog background color. -->
+ <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceInverse</color>
+
+ <color name="settingslib_colorSurfaceHeader">@color/settingslib_materialColorSurfaceVariant</color>
+
+ <color name="settingslib_text_color_preference_category_title">@color/settingslib_materialColorPrimary</color>
+
<color name="settingslib_materialColorSurfaceContainerLowest">@android:color/system_surface_container_lowest_dark</color>
<color name="settingslib_materialColorOnSecondaryContainer">@android:color/system_on_secondary_container_dark</color>
<color name="settingslib_materialColorOnTertiaryContainer">@android:color/system_on_tertiary_container_dark</color>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
index fe47e858b747..7706e0e8a296 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
@@ -35,49 +35,57 @@
<color name="settingslib_track_off_color">@color/settingslib_switch_track_off</color>
<!-- Dialog accent color -->
+ <!--Deprecated. After sdk 35 don't use it, using materialColorPrimary-->
<color name="settingslib_dialog_accent">@android:color/system_accent1_600</color>
<!-- Dialog background color -->
<color name="settingslib_dialog_background">@color/settingslib_surface_light</color>
<!-- Dialog error color. -->
<color name="settingslib_dialog_colorError">#d93025</color> <!-- Red 600 -->
+ <!--Deprecated. After sdk 35 don't use it.-->
<color name="settingslib_colorSurfaceVariant">@android:color/system_neutral2_100</color>
<color name="settingslib_colorSurfaceHeader">@android:color/system_neutral1_100</color>
+ <!--Deprecated. After sdk 35 don't use it.-->
<color name="settingslib_accent_device_default_dark">@android:color/system_accent1_100</color>
+ <!--Deprecated. After sdk 35 don't use it.-->
<color name="settingslib_accent_device_default_light">@android:color/system_accent1_600</color>
+ <!--Deprecated. After sdk 35 don't use it.-->
<color name="settingslib_primary_dark_device_default_settings">@android:color/system_neutral1_900</color>
+ <!--Deprecated. After sdk 35 don't use it.-->
<color name="settingslib_primary_device_default_settings_light">@android:color/system_neutral1_50</color>
+ <!--Deprecated. After sdk 35 don't use it.-->
<color name="settingslib_accent_primary_device_default">@android:color/system_accent1_100</color>
<!-- copy from accent_primary_variant_light_device_default-->
+ <!-- TODO: deprecate it after moving into partner code-->
<color name="settingslib_accent_primary_variant">@android:color/system_accent1_600</color>
-
+ <!--Deprecated. After sdk 35 don't use it.-->
<color name="settingslib_accent_secondary_device_default">@android:color/system_accent2_100</color>
-
+ <!--Deprecated. After sdk 35 don't use it.using materialColorOnSurfaceInverse in dark theme-->
<color name="settingslib_background_device_default_dark">@android:color/system_neutral1_900</color>
-
+ <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurfaceInverse in light theme-->
<color name="settingslib_background_device_default_light">@android:color/system_neutral1_50</color>
-
+ <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurface-->
<color name="settingslib_text_color_primary_device_default">@android:color/system_neutral1_900</color>
-
+ <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurfaceVariant-->
<color name="settingslib_text_color_secondary_device_default">@android:color/system_neutral2_700</color>
<color name="settingslib_text_color_preference_category_title">@android:color/system_accent1_600</color>
<color name="settingslib_ripple_color">?android:attr/colorControlHighlight</color>
- <color name="settingslib_material_grey_900">#ff212121</color>
-
+ <!--Deprecated. After sdk 35 don't use it.-->
<color name="settingslib_colorAccentPrimary">@color/settingslib_accent_primary_device_default</color>
-
+ <!--Deprecated. After sdk 35 don't use it.-->
<color name="settingslib_colorAccentSecondary">@color/settingslib_accent_secondary_device_default</color>
+ <!--Deprecated. After sdk 35, don't use it-->
<color name="settingslib_colorSurface">@color/settingslib_surface_light</color>
<color name="settingslib_spinner_title_color">@android:color/system_neutral1_900</color>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
index e4befc290939..a9534c3d5ca6 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
@@ -17,6 +17,7 @@
<resources>
<style name="PreferenceTheme.SettingsLib" parent="@style/PreferenceThemeOverlay">
<item name="preferenceCategoryTitleTextAppearance">@style/TextAppearance.CategoryTitle.SettingsLib</item>
+ <item name="preferenceCategoryTitleTextColor">@color/settingslib_text_color_preference_category_title</item>
<item name="preferenceScreenStyle">@style/SettingsPreferenceScreen.SettingsLib</item>
<item name="preferenceCategoryStyle">@style/SettingsCategoryPreference.SettingsLib</item>
<item name="preferenceStyle">@style/SettingsPreference.SettingsLib</item>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v33/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v33/themes.xml
index 24e3c46b39ce..fb637fb007ae 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v33/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v33/themes.xml
@@ -16,9 +16,11 @@
-->
<resources>
- <style name="Theme.SettingsBase" parent="Theme.SettingsBase_v31" >
+ <style name="Theme.SettingsBase_v33" parent="Theme.SettingsBase_v31" >
<item name="android:spinnerStyle">@style/Spinner.SettingsLib</item>
<item name="android:spinnerItemStyle">@style/SpinnerItem.SettingsLib</item>
<item name="android:spinnerDropDownItemStyle">@style/SpinnerDropDownItem.SettingsLib</item>
</style>
+
+ <style name="Theme.SettingsBase" parent="Theme.SettingsBase_v33" />
</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml
index 3709b5d13056..185ac3e1fe73 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml
@@ -39,8 +39,8 @@
<!-- Material next track outline color-->
<color name="settingslib_track_online_color">@color/settingslib_switch_track_outline_color</color>
-
+ <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurface-->
<color name="settingslib_text_color_primary_device_default">@android:color/system_on_surface_light</color>
-
+ <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurfaceVariant-->
<color name="settingslib_text_color_secondary_device_default">@android:color/system_on_surface_variant_light</color>
</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
index 2691344bfdb0..2a6499aaab37 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
@@ -16,12 +16,42 @@
-->
<resources>
+ <!-- Material next state on color-->
+ <color name="settingslib_state_on_color">@color/settingslib_materialColorPrimaryContainer</color>
+
+ <!-- Material next state off color-->
+ <color name="settingslib_state_off_color">@color/settingslib_materialColorPrimaryContainer</color>
+
+ <!-- Material next thumb disable color-->
+ <color name="settingslib_thumb_disabled_color">@color/settingslib_materialColorOutline</color>
+
+ <!-- Material next thumb off color-->
+ <color name="settingslib_thumb_on_color">@color/settingslib_materialColorOnPrimary</color>
+
+ <!-- Material next thumb off color-->
+ <color name="settingslib_thumb_off_color">@color/settingslib_materialColorOutline</color>
+
+ <!-- Material next track on color-->
+ <color name="settingslib_track_on_color">@color/settingslib_materialColorPrimary</color>
+
+ <!-- Material next track off color-->
+ <color name="settingslib_track_off_color">@color/settingslib_materialColorSurfaceContainerHighest</color>
+
+ <!-- Dialog background color. -->
+ <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceInverse</color>
+
+ <!-- Material next track outline color-->
+ <color name="settingslib_track_online_color">@color/settingslib_switch_track_outline_color</color>
+
+ <color name="settingslib_colorSurfaceHeader">@color/settingslib_materialColorSurfaceVariant</color>
+
+ <color name="settingslib_text_color_preference_category_title">@color/settingslib_materialColorPrimary</color>
+
<!-- The text color of spinner title -->
<color name="settingslib_spinner_title_color">@color/settingslib_materialColorOnPrimaryContainer</color>
<!-- The text color of dropdown item title -->
<color name="settingslib_spinner_dropdown_color">@color/settingslib_materialColorOnPrimaryContainer</color>
-
<color name="settingslib_materialColorOnSecondaryFixedVariant">@android:color/system_on_secondary_fixed_variant</color>
<color name="settingslib_materialColorOnTertiaryFixedVariant">@android:color/system_on_tertiary_fixed_variant</color>
<color name="settingslib_materialColorSurfaceContainerLowest">@android:color/system_surface_container_lowest_light</color>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
index 01dfd7d71964..cdd5c2500693 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
@@ -1,31 +1,28 @@
<?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.
+ 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>
- <style name="Theme.SettingsBase" parent="Theme.SettingsBase_v31" >
- <item name="android:spinnerStyle">@style/Spinner.SettingsLib</item>
- <item name="android:spinnerItemStyle">@style/SpinnerItem.SettingsLib</item>
- <item name="android:spinnerDropDownItemStyle">@style/SpinnerDropDownItem.SettingsLib</item>
-
+ <style name="Theme.SettingsBase_v35" parent="Theme.SettingsBase_v33" >
<item name="android:colorAccent">@color/settingslib_materialColorPrimary</item>
- <!-- component module background -->
<item name="android:colorBackground">@color/settingslib_materialColorSurfaceContainer</item>
<item name="android:textColorPrimary">@color/settingslib_materialColorOnSurface</item>
<item name="android:textColorSecondary">@color/settingslib_materialColorOnSurfaceVariant</item>
<item name="android:textColorTertiary">@color/settingslib_materialColorOutline</item>
</style>
+
+ <style name="Theme.SettingsBase" parent="Theme.SettingsBase_v35" />
</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
index bdc6a6890cbd..22844362f949 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
@@ -40,12 +40,13 @@ fun SettingsOutlinedTextField(
singleLine: Boolean = true,
enabled: Boolean = true,
shape: Shape = OutlinedTextFieldDefaults.shape,
+ modifier: Modifier = Modifier
+ .fillMaxWidth()
+ .padding(SettingsDimension.textFieldPadding),
onTextChange: (String) -> Unit
) {
OutlinedTextField(
- modifier = Modifier
- .fillMaxWidth()
- .padding(SettingsDimension.textFieldPadding),
+ modifier = modifier,
value = value,
onValueChange = onTextChange,
label = {
diff --git a/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java b/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java
index 712f6f03a441..ea3dbd925792 100644
--- a/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java
+++ b/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextUtils;
+import android.text.method.LinkMovementMethod;
import android.text.style.AbsoluteSizeSpan;
import android.util.AttributeSet;
import android.view.View;
@@ -174,6 +175,7 @@ public class UsageProgressBarPreference extends Preference {
bottomSummary.setVisibility(View.GONE);
} else {
bottomSummary.setVisibility(View.VISIBLE);
+ bottomSummary.setMovementMethod(LinkMovementMethod.getInstance());
bottomSummary.setText(mBottomSummary);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java b/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java
index f73081a4eb60..169c3306c393 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java
@@ -51,6 +51,7 @@ public class RecentAppOpsAccess {
};
private static final int[] MICROPHONE_OPS = new int[]{
AppOpsManager.OP_RECORD_AUDIO,
+ AppOpsManager.OP_PHONE_CALL_MICROPHONE,
};
private static final int[] CAMERA_OPS = new int[]{
AppOpsManager.OP_CAMERA,
@@ -144,6 +145,11 @@ public class RecentAppOpsAccess {
if (!showSystemApps) {
for (int op : mOps) {
final String permission = AppOpsManager.opToPermission(op);
+ if (permission == null) {
+ // Some ops like OP_PHONE_CALL_MICROPHONE don't have corresponding
+ // permissions. No need to check in this case.
+ continue;
+ }
final int permissionFlags = mPackageManager.getPermissionFlags(permission,
packageName,
user);
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
index 8fd4e912e04a..822a60889931 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
@@ -18,14 +18,18 @@ package com.android.settingslib.fuelgauge;
import static android.provider.DeviceConfig.NAMESPACE_ACTIVITY_MANAGER;
+import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.os.IDeviceIdleController;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.telecom.DefaultDialerManager;
import android.text.TextUtils;
@@ -121,6 +125,14 @@ public class PowerAllowlistBackend {
return true;
}
+ if (android.app.admin.flags.Flags.disallowUserControlBgUsageFix()) {
+ // App is subject to DevicePolicyManager.setUserControlDisabledPackages() policy.
+ final int userId = UserHandle.getUserId(uid);
+ if (mAppContext.getPackageManager().isPackageStateProtected(pkg, userId)) {
+ return true;
+ }
+ }
+
return false;
}
@@ -163,27 +175,77 @@ public class PowerAllowlistBackend {
/**
* Add app into power save allow list.
- * @param pkg packageName
+ * @param pkg packageName of the app
*/
+ // TODO: Fix all callers to pass in UID
public void addApp(String pkg) {
+ addApp(pkg, Process.INVALID_UID);
+ }
+
+ /**
+ * Add app into power save allow list.
+ * @param pkg packageName of the app
+ * @param uid uid of the app
+ */
+ public void addApp(String pkg, int uid) {
try {
+ if (android.app.Flags.appRestrictionsApi()) {
+ if (uid == Process.INVALID_UID) {
+ uid = mAppContext.getSystemService(PackageManager.class).getPackageUid(pkg, 0);
+ }
+ final boolean wasInList = isAllowlisted(pkg, uid);
+
+ if (!wasInList) {
+ mAppContext.getSystemService(ActivityManager.class).noteAppRestrictionEnabled(
+ pkg, uid, ActivityManager.RESTRICTION_LEVEL_EXEMPTED,
+ true, ActivityManager.RESTRICTION_REASON_USER,
+ "settings", 0);
+ }
+ }
+
mDeviceIdleService.addPowerSaveWhitelistApp(pkg);
mAllowlistedApps.add(pkg);
} catch (RemoteException e) {
Log.w(TAG, "Unable to reach IDeviceIdleController", e);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Unable to find package", e);
}
}
/**
* Remove package from power save allow list.
- * @param pkg
+ * @param pkg packageName of the app
*/
public void removeApp(String pkg) {
+ removeApp(pkg, Process.INVALID_UID);
+ }
+
+ /**
+ * Remove package from power save allow list.
+ * @param pkg packageName of the app
+ * @param uid uid of the app
+ */
+ public void removeApp(String pkg, int uid) {
try {
+ if (android.app.Flags.appRestrictionsApi()) {
+ if (uid == Process.INVALID_UID) {
+ uid = mAppContext.getSystemService(PackageManager.class).getPackageUid(pkg, 0);
+ }
+ final boolean wasInList = isAllowlisted(pkg, uid);
+ if (wasInList) {
+ mAppContext.getSystemService(ActivityManager.class).noteAppRestrictionEnabled(
+ pkg, uid, ActivityManager.RESTRICTION_LEVEL_EXEMPTED,
+ false, ActivityManager.RESTRICTION_REASON_USER,
+ "settings", 0);
+ }
+ }
+
mDeviceIdleService.removePowerSaveWhitelistApp(pkg);
mAllowlistedApps.remove(pkg);
} catch (RemoteException e) {
Log.w(TAG, "Unable to reach IDeviceIdleController", e);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Unable to find package", e);
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java
index b656253ba147..0d318c346521 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java
@@ -96,7 +96,7 @@ public class PowerAllowlistBackendTest {
assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_ONE}, UID)).isTrue();
assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_TWO}, UID)).isFalse();
- mPowerAllowlistBackend.addApp(PACKAGE_TWO);
+ mPowerAllowlistBackend.addApp(PACKAGE_TWO, UID);
verify(mDeviceIdleService, atLeastOnce()).addPowerSaveWhitelistApp(PACKAGE_TWO);
assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_ONE, UID)).isTrue();
@@ -104,7 +104,7 @@ public class PowerAllowlistBackendTest {
assertThat(mPowerAllowlistBackend.isAllowlisted(
new String[] {PACKAGE_ONE, PACKAGE_TWO}, UID)).isTrue();
- mPowerAllowlistBackend.removeApp(PACKAGE_TWO);
+ mPowerAllowlistBackend.removeApp(PACKAGE_TWO, UID);
verify(mDeviceIdleService, atLeastOnce()).removePowerSaveWhitelistApp(PACKAGE_TWO);
assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_ONE, UID)).isTrue();
@@ -112,7 +112,7 @@ public class PowerAllowlistBackendTest {
assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_ONE}, UID)).isTrue();
assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_TWO}, UID)).isFalse();
- mPowerAllowlistBackend.removeApp(PACKAGE_ONE);
+ mPowerAllowlistBackend.removeApp(PACKAGE_ONE, UID);
verify(mDeviceIdleService, atLeastOnce()).removePowerSaveWhitelistApp(PACKAGE_ONE);
assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_ONE, UID)).isFalse();
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index a33c160924e7..75c0cec1ad72 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -272,6 +272,7 @@ public class SecureSettings {
Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
Settings.Secure.AUDIO_DEVICE_INVENTORY,
Settings.Secure.SCREEN_RESOLUTION_MODE,
- Settings.Secure.ACCESSIBILITY_FLOATING_MENU_TARGETS
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_TARGETS,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 1bff59289326..8faf917cadc2 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -430,5 +430,7 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.AUDIO_DEVICE_INVENTORY, ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.SCREEN_RESOLUTION_MODE, new InclusiveIntegerRangeValidator(
Secure.RESOLUTION_MODE_UNKNOWN, Secure.RESOLUTION_MODE_FULL));
+ VALIDATORS.put(Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL,
+ new InclusiveIntegerRangeValidator(0, 10));
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 46cee6b1cc0a..fa9b279581c9 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1760,6 +1760,9 @@ class SettingsProtoDumpUtil {
Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER,
SecureSettingsProto.Accessibility.DISPLAY_DALTONIZER);
dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL,
+ SecureSettingsProto.Accessibility.DISPLAY_DALTONIZER_SATURATION_LEVEL);
+ dumpSetting(s, p,
Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
SecureSettingsProto.Accessibility.DISPLAY_INVERSION_ENABLED);
dumpSetting(s, p,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b94e224850aa..46bf494f2b1a 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -934,6 +934,9 @@
<!-- Permission required for Cts test - CtsSettingsTestCases -->
<uses-permission android:name="android.permission.PREPARE_FACTORY_RESET" />
+ <!-- Permission required for CTS test - FileIntegrityManagerTest -->
+ <uses-permission android:name="android.permission.SETUP_FSVERITY" />
+
<application
android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index c88c3731aa10..5e11e1aa6a26 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -363,6 +363,7 @@ android_library {
"SystemUICustomizationTestUtils",
"androidx.compose.runtime_runtime",
"kosmos",
+ "androidx.test.rules",
],
libs: [
"android.test.runner",
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index c979d053617a..c2e4b82acf57 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -784,3 +784,23 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "slice_broadcast_relay_in_background"
+ namespace: "systemui"
+ description: "Move handling of slice broadcast relay broadcasts to background threads"
+ bug: "334767208"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "register_battery_controller_receivers_in_corestartable"
+ namespace: "systemui"
+ description: "Decide whether to register the receivers in battery controller impl in the BatteryControllerStartable corestartable."
+ bug: "307517093"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffect.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffect.kt
new file mode 100644
index 000000000000..ffa2b4662f33
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffect.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.surfaceeffects.revealeffect
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.graphics.RenderEffect
+import androidx.core.graphics.ColorUtils
+import com.android.systemui.surfaceeffects.RenderEffectDrawCallback
+import com.android.systemui.surfaceeffects.utils.MathUtils
+import kotlin.math.max
+import kotlin.math.min
+
+/** Creates a reveal effect with a circular ripple sparkles on top. */
+class RippleRevealEffect(
+ private val config: RippleRevealEffectConfig,
+ private val renderEffectCallback: RenderEffectDrawCallback,
+ private val stateChangedCallback: AnimationStateChangedCallback? = null
+) {
+ private val rippleRevealShader = RippleRevealShader().apply { applyConfig(config) }
+ private val animator: ValueAnimator = ValueAnimator.ofFloat(0f, 1f)
+
+ fun play() {
+ if (animator.isRunning) {
+ return
+ }
+
+ animator.duration = config.duration.toLong()
+ animator.addUpdateListener { updateListener ->
+ val playTime = updateListener.currentPlayTime.toFloat()
+ rippleRevealShader.setTime(playTime * TIME_SCALE_FACTOR)
+
+ // Compute radius.
+ val progress = updateListener.animatedValue as Float
+ val innerRad = MathUtils.lerp(config.innerRadiusStart, config.innerRadiusEnd, progress)
+ val outerRad = MathUtils.lerp(config.outerRadiusStart, config.outerRadiusEnd, progress)
+ rippleRevealShader.setInnerRadius(innerRad)
+ rippleRevealShader.setOuterRadius(outerRad)
+
+ // Compute alphas.
+ val innerAlphaProgress =
+ MathUtils.constrainedMap(
+ 1f,
+ 0f,
+ config.innerFadeOutStart,
+ config.duration,
+ playTime
+ )
+ val outerAlphaProgress =
+ MathUtils.constrainedMap(
+ 1f,
+ 0f,
+ config.outerFadeOutStart,
+ config.duration,
+ playTime
+ )
+ val innerAlpha = MathUtils.lerp(0f, 255f, innerAlphaProgress)
+ val outerAlpha = MathUtils.lerp(0f, 255f, outerAlphaProgress)
+
+ val innerColor = ColorUtils.setAlphaComponent(config.innerColor, innerAlpha.toInt())
+ val outerColor = ColorUtils.setAlphaComponent(config.outerColor, outerAlpha.toInt())
+ rippleRevealShader.setInnerColor(innerColor)
+ rippleRevealShader.setOuterColor(outerColor)
+
+ // Pass in progresses since those functions take in normalized alpha values.
+ rippleRevealShader.setBackgroundAlpha(max(innerAlphaProgress, outerAlphaProgress))
+ rippleRevealShader.setSparkleAlpha(min(innerAlphaProgress, outerAlphaProgress))
+
+ // Trigger draw callback.
+ renderEffectCallback.onDraw(
+ RenderEffect.createRuntimeShaderEffect(
+ rippleRevealShader,
+ RippleRevealShader.BACKGROUND_UNIFORM
+ )
+ )
+ }
+ animator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ stateChangedCallback?.onAnimationEnd()
+ }
+ }
+ )
+ animator.start()
+ stateChangedCallback?.onAnimationStart()
+ }
+
+ interface AnimationStateChangedCallback {
+ fun onAnimationStart()
+ fun onAnimationEnd()
+ }
+
+ private companion object {
+ private const val TIME_SCALE_FACTOR = 0.00175f
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffectConfig.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffectConfig.kt
new file mode 100644
index 000000000000..9675f19613a8
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffectConfig.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.surfaceeffects.revealeffect
+
+import android.graphics.Color
+
+/** Defines parameters needed for [RippleRevealEffect]. */
+data class RippleRevealEffectConfig(
+ /** Total duration of the animation. */
+ val duration: Float = 0f,
+ /** Timestamp of when the inner mask starts fade out. (Linear fadeout) */
+ val innerFadeOutStart: Float = 0f,
+ /** Timestamp of when the outer mask starts fade out. (Linear fadeout) */
+ val outerFadeOutStart: Float = 0f,
+ /** Center x position of the effect. */
+ val centerX: Float = 0f,
+ /** Center y position of the effect. */
+ val centerY: Float = 0f,
+ /** Start radius of the inner circle. */
+ val innerRadiusStart: Float = 0f,
+ /** End radius of the inner circle. */
+ val innerRadiusEnd: Float = 0f,
+ /** Start radius of the outer circle. */
+ val outerRadiusStart: Float = 0f,
+ /** End radius of the outer circle. */
+ val outerRadiusEnd: Float = 0f,
+ /**
+ * Pixel density of the display. Do not pass a random value. The value must come from
+ * [context.resources.displayMetrics.density].
+ */
+ val pixelDensity: Float = 1f,
+ /**
+ * The amount the circle masks should be softened. Higher value will make the edge of the circle
+ * mask soft.
+ */
+ val blurAmount: Float = 0f,
+ /** Color of the inner circle mask. */
+ val innerColor: Int = Color.WHITE,
+ /** Color of the outer circle mask. */
+ val outerColor: Int = Color.WHITE,
+ /** Multiplier to make the sparkles visible. */
+ val sparkleStrength: Float = SPARKLE_STRENGTH,
+ /** Size of the sparkle. Expected range [0, 1]. */
+ val sparkleScale: Float = SPARKLE_SCALE
+) {
+ /** Default parameters. */
+ companion object {
+ const val SPARKLE_STRENGTH: Float = 0.3f
+ const val SPARKLE_SCALE: Float = 0.8f
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealShader.kt
new file mode 100644
index 000000000000..a3f979542055
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealShader.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.systemui.surfaceeffects.revealeffect
+
+import android.graphics.RuntimeShader
+import com.android.systemui.surfaceeffects.shaderutil.SdfShaderLibrary
+import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
+
+/** Circular reveal effect with sparkles. */
+class RippleRevealShader : RuntimeShader(SHADER) {
+ // language=AGSL
+ companion object {
+ const val BACKGROUND_UNIFORM = "in_dst"
+ private const val MAIN =
+ """
+ uniform shader ${BACKGROUND_UNIFORM};
+ uniform half in_dstAlpha;
+ uniform half in_time;
+ uniform vec2 in_center;
+ uniform half in_innerRadius;
+ uniform half in_outerRadius;
+ uniform half in_sparkleStrength;
+ uniform half in_blur;
+ uniform half in_pixelDensity;
+ uniform half in_sparkleScale;
+ uniform half in_sparkleAlpha;
+ layout(color) uniform vec4 in_innerColor;
+ layout(color) uniform vec4 in_outerColor;
+
+ vec4 main(vec2 p) {
+ half innerMask = soften(sdCircle(p - in_center, in_innerRadius), in_blur);
+ half outerMask = soften(sdCircle(p - in_center, in_outerRadius), in_blur);
+
+ // Flip it since we are interested in the circle.
+ innerMask = 1.-innerMask;
+ outerMask = 1.-outerMask;
+
+ // Color two circles using the mask.
+ vec4 inColor = vec4(in_innerColor.rgb, 1.) * in_innerColor.a;
+ vec4 outColor = vec4(in_outerColor.rgb, 1.) * in_outerColor.a;
+ vec4 blend = mix(inColor, outColor, innerMask);
+
+ vec4 dst = vec4(in_dst.eval(p).rgb, 1.);
+ dst *= in_dstAlpha;
+
+ blend *= blend.a;
+ // Do normal blend with the background.
+ blend = blend + dst * (1. - blend.a);
+
+ half sparkle =
+ sparkles(p - mod(p, in_pixelDensity * in_sparkleScale), in_time);
+ // Add sparkles using additive blending.
+ blend += sparkle * in_sparkleStrength * in_sparkleAlpha;
+
+ // Mask everything at the end.
+ blend *= outerMask;
+
+ return blend;
+ }
+ """
+
+ private const val SHADER =
+ ShaderUtilLibrary.SHADER_LIB +
+ SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
+ SdfShaderLibrary.CIRCLE_SDF +
+ MAIN
+ }
+
+ fun applyConfig(config: RippleRevealEffectConfig) {
+ setCenter(config.centerX, config.centerY)
+ setInnerRadius(config.innerRadiusStart)
+ setOuterRadius(config.outerRadiusStart)
+ setBlurAmount(config.blurAmount)
+ setPixelDensity(config.pixelDensity)
+ setSparkleScale(config.sparkleScale)
+ setSparkleStrength(config.sparkleStrength)
+ setInnerColor(config.innerColor)
+ setOuterColor(config.outerColor)
+ }
+
+ fun setTime(time: Float) {
+ setFloatUniform("in_time", time)
+ }
+
+ fun setCenter(centerX: Float, centerY: Float) {
+ setFloatUniform("in_center", centerX, centerY)
+ }
+
+ fun setInnerRadius(radius: Float) {
+ setFloatUniform("in_innerRadius", radius)
+ }
+
+ fun setOuterRadius(radius: Float) {
+ setFloatUniform("in_outerRadius", radius)
+ }
+
+ fun setBlurAmount(blurAmount: Float) {
+ setFloatUniform("in_blur", blurAmount)
+ }
+
+ fun setPixelDensity(density: Float) {
+ setFloatUniform("in_pixelDensity", density)
+ }
+
+ fun setSparkleScale(scale: Float) {
+ setFloatUniform("in_sparkleScale", scale)
+ }
+
+ fun setSparkleStrength(strength: Float) {
+ setFloatUniform("in_sparkleStrength", strength)
+ }
+
+ fun setInnerColor(color: Int) {
+ setColorUniform("in_innerColor", color)
+ }
+
+ fun setOuterColor(color: Int) {
+ setColorUniform("in_outerColor", color)
+ }
+
+ /** Sets the background alpha. Range [0,1]. */
+ fun setBackgroundAlpha(alpha: Float) {
+ setFloatUniform("in_dstAlpha", alpha)
+ }
+
+ /** Sets the sparkle alpha. Range [0,1]. */
+ fun setSparkleAlpha(alpha: Float) {
+ setFloatUniform("in_sparkleAlpha", alpha)
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/utils/MathUtils.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/utils/MathUtils.kt
new file mode 100644
index 000000000000..1411c32b813b
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/utils/MathUtils.kt
@@ -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.surfaceeffects.utils
+
+/** Copied from android.utils.MathUtils */
+object MathUtils {
+ fun constrainedMap(
+ rangeMin: Float,
+ rangeMax: Float,
+ valueMin: Float,
+ valueMax: Float,
+ value: Float
+ ): Float {
+ return lerp(rangeMin, rangeMax, lerpInvSat(valueMin, valueMax, value))
+ }
+
+ fun lerp(start: Float, stop: Float, amount: Float): Float {
+ return start + (stop - start) * amount
+ }
+
+ fun lerpInv(a: Float, b: Float, value: Float): Float {
+ return if (a != b) (value - a) / (b - a) else 0.0f
+ }
+
+ fun saturate(value: Float): Float {
+ return constrain(value, 0.0f, 1.0f)
+ }
+
+ fun lerpInvSat(a: Float, b: Float, value: Float): Float {
+ return saturate(lerpInv(a, b, value))
+ }
+
+ fun constrain(amount: Float, low: Float, high: Float): Float {
+ return if (amount < low) low else if (amount > high) high else amount
+ }
+}
diff --git a/packages/SystemUI/checks/Android.bp b/packages/SystemUI/checks/Android.bp
index 4cbc18c3a295..f65d797c5df8 100644
--- a/packages/SystemUI/checks/Android.bp
+++ b/packages/SystemUI/checks/Android.bp
@@ -24,10 +24,7 @@ package {
java_library_host {
name: "SystemUILintChecker",
- srcs: [
- "src/**/*.kt",
- "src/**/*.java",
- ],
+ srcs: ["src/**/*.kt"],
plugins: ["auto_service_plugin"],
libs: [
"auto_service_annotations",
@@ -38,35 +35,13 @@ java_library_host {
java_test_host {
name: "SystemUILintCheckerTest",
- srcs: [
- "tests/**/*.kt",
- "tests/**/*.java",
- ],
+ defaults: ["AndroidLintCheckerTestDefaults"],
+ srcs: ["tests/**/*.kt"],
data: [
":framework",
- ":androidx.annotation_annotation",
+ ":androidx.annotation_annotation-nodeps",
],
static_libs: [
"SystemUILintChecker",
- "junit",
- "lint",
- "lint_tests",
],
- test_options: {
- unit_test: true,
- tradefed_options: [
- {
- // lint bundles in some classes that were built with older versions
- // of libraries, and no longer load. Since tradefed tries to load
- // all classes in the jar to look for tests, it crashes loading them.
- // Exclude these classes from tradefed's search.
- name: "exclude-paths",
- value: "org/apache",
- },
- {
- name: "exclude-paths",
- value: "META-INF",
- },
- ],
- },
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DumpableNotRegisteredDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DumpableNotRegisteredDetector.kt
index 30e2a2527a93..f9bf306b55fe 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DumpableNotRegisteredDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DumpableNotRegisteredDetector.kt
@@ -34,7 +34,6 @@ import org.jetbrains.uast.UClass
* Checks if any class has implemented the `Dumpable` interface but has not registered itself with
* the `DumpManager`.
*/
-@Suppress("UnstableApiUsage")
class DumpableNotRegisteredDetector : Detector(), SourceCodeScanner {
private var isDumpable: Boolean = false
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
index 5840e8f8dfb6..024c3946b05e 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
@@ -59,7 +59,7 @@ class RegisterReceiverViaContextDetector : Detector(), SourceCodeScanner {
`BroadcastDispatcher` instead, which registers the receiver on a \
background thread. `BroadcastDispatcher` also improves our visibility \
into ANRs.""",
- moreInfo = "go/identifying-broadcast-threads",
+ moreInfo = "http://go/identifying-broadcast-threads",
category = Category.PERFORMANCE,
priority = 8,
severity = Severity.WARNING,
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
index 141dd0535986..f3b24a35f91f 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt
@@ -16,15 +16,8 @@
package com.android.internal.systemui.lint
-import com.android.annotations.NonNull
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest.java
import com.android.tools.lint.checks.infrastructure.TestFiles.LibraryReferenceTestFile
import java.io.File
-import org.intellij.lang.annotations.Language
-
-@Suppress("UnstableApiUsage")
-@NonNull
-private fun indentedJava(@NonNull @Language("JAVA") source: String) = java(source).indented()
/*
* This file contains stubs of framework APIs and System UI classes for testing purposes only. The
@@ -33,16 +26,5 @@ private fun indentedJava(@NonNull @Language("JAVA") source: String) = java(sourc
internal val androidStubs =
arrayOf(
LibraryReferenceTestFile(File("framework.jar").canonicalFile),
- LibraryReferenceTestFile(File("androidx.annotation_annotation.jar").canonicalFile),
- indentedJava(
- """
-package com.android.systemui.settings;
-import android.content.pm.UserInfo;
-
-public interface UserTracker {
- int getUserId();
- UserInfo getUserInfo();
-}
-"""
- ),
+ LibraryReferenceTestFile(File("androidx.annotation_annotation-nodeps.jar").canonicalFile),
)
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
index 4c4185dc82f9..c9bc8b35a34b 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
@@ -19,17 +19,14 @@ package com.android.internal.systemui.lint
import com.android.tools.lint.checks.infrastructure.TestFiles
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
-import org.junit.Ignore
import org.junit.Test
-@Suppress("UnstableApiUsage")
class BindServiceOnMainThreadDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = BindServiceOnMainThreadDetector()
override fun getIssues(): List<Issue> = listOf(BindServiceOnMainThreadDetector.ISSUE)
- @Ignore
@Test
fun testBindService() {
lint()
@@ -37,7 +34,9 @@ class BindServiceOnMainThreadDetectorTest : SystemUILintDetectorTest() {
TestFiles.java(
"""
package test.pkg;
+
import android.content.Context;
+ import android.content.Intent;
public class TestClass {
public void bind(Context context) {
@@ -48,13 +47,13 @@ class BindServiceOnMainThreadDetectorTest : SystemUILintDetectorTest() {
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BindServiceOnMainThreadDetector.ISSUE)
.run()
.expect(
"""
- src/test/pkg/TestClass.java:7: Warning: This method should be annotated with @WorkerThread because it calls bindService [BindServiceOnMainThread]
+ src/test/pkg/TestClass.java:9: Warning: This method should be annotated with @WorkerThread because it calls bindService [BindServiceOnMainThread]
context.bindService(intent, null, 0);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
@@ -62,7 +61,6 @@ class BindServiceOnMainThreadDetectorTest : SystemUILintDetectorTest() {
)
}
- @Ignore
@Test
fun testBindServiceAsUser() {
lint()
@@ -70,7 +68,9 @@ class BindServiceOnMainThreadDetectorTest : SystemUILintDetectorTest() {
TestFiles.java(
"""
package test.pkg;
+
import android.content.Context;
+ import android.content.Intent;
import android.os.UserHandle;
public class TestClass {
@@ -82,13 +82,13 @@ class BindServiceOnMainThreadDetectorTest : SystemUILintDetectorTest() {
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BindServiceOnMainThreadDetector.ISSUE)
.run()
.expect(
"""
- src/test/pkg/TestClass.java:8: Warning: This method should be annotated with @WorkerThread because it calls bindServiceAsUser [BindServiceOnMainThread]
+ src/test/pkg/TestClass.java:10: Warning: This method should be annotated with @WorkerThread because it calls bindServiceAsUser [BindServiceOnMainThread]
context.bindServiceAsUser(intent, null, 0, UserHandle.ALL);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
@@ -114,7 +114,7 @@ class BindServiceOnMainThreadDetectorTest : SystemUILintDetectorTest() {
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BindServiceOnMainThreadDetector.ISSUE)
.run()
@@ -147,7 +147,7 @@ class BindServiceOnMainThreadDetectorTest : SystemUILintDetectorTest() {
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BindServiceOnMainThreadDetector.ISSUE)
.run()
@@ -181,7 +181,7 @@ class BindServiceOnMainThreadDetectorTest : SystemUILintDetectorTest() {
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BindServiceOnMainThreadDetector.ISSUE)
.run()
@@ -219,12 +219,10 @@ class BindServiceOnMainThreadDetectorTest : SystemUILintDetectorTest() {
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BindServiceOnMainThreadDetector.ISSUE)
.run()
.expectClean()
}
-
- private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
index 30b68f7e7a75..3788dda7975f 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
@@ -21,7 +21,6 @@ import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class BroadcastSentViaContextDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = BroadcastSentViaContextDetector()
@@ -30,7 +29,6 @@ class BroadcastSentViaContextDetectorTest : SystemUILintDetectorTest() {
@Test
fun testSendBroadcast() {
- println(stubs.size)
lint()
.files(
TestFiles.java(
@@ -47,7 +45,7 @@ class BroadcastSentViaContextDetectorTest : SystemUILintDetectorTest() {
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
@@ -80,7 +78,7 @@ class BroadcastSentViaContextDetectorTest : SystemUILintDetectorTest() {
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
@@ -114,7 +112,7 @@ class BroadcastSentViaContextDetectorTest : SystemUILintDetectorTest() {
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
@@ -149,7 +147,7 @@ class BroadcastSentViaContextDetectorTest : SystemUILintDetectorTest() {
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
@@ -176,7 +174,7 @@ class BroadcastSentViaContextDetectorTest : SystemUILintDetectorTest() {
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
@@ -201,12 +199,10 @@ class BroadcastSentViaContextDetectorTest : SystemUILintDetectorTest() {
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(BroadcastSentViaContextDetector.ISSUE)
.run()
.expectClean()
}
-
- private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt
index ff150c8cba13..2c2032123f05 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt
@@ -21,11 +21,8 @@ import com.android.tools.lint.checks.infrastructure.TestFiles
import com.android.tools.lint.checks.infrastructure.TestMode
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
-import org.junit.Ignore
import org.junit.Test
-@Suppress("UnstableApiUsage")
-@Ignore("b/254533331")
class CleanArchitectureDependencyViolationDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector {
return CleanArchitectureDependencyViolationDetector()
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
index ee6e0ce72713..0652e6908cc6 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
@@ -24,7 +24,6 @@ import com.android.tools.lint.detector.api.Scope
import java.util.EnumSet
import org.junit.Test
-@Suppress("UnstableApiUsage")
class DemotingTestWithoutBugDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = DemotingTestWithoutBugDetector()
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DumpableNotRegisteredDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DumpableNotRegisteredDetectorTest.kt
index 3d6cbc749569..6c6c26355ad9 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DumpableNotRegisteredDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DumpableNotRegisteredDetectorTest.kt
@@ -21,7 +21,6 @@ import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class DumpableNotRegisteredDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = DumpableNotRegisteredDetector()
@@ -37,7 +36,8 @@ class DumpableNotRegisteredDetectorTest : SystemUILintDetectorTest() {
class SomeClass() {
}
- """.trimIndent()
+ """
+ .trimIndent()
),
*stubs,
)
@@ -67,7 +67,8 @@ class DumpableNotRegisteredDetectorTest : SystemUILintDetectorTest() {
pw.println("testDump");
}
}
- """.trimIndent()
+ """
+ .trimIndent()
),
*stubs,
)
@@ -97,7 +98,8 @@ class DumpableNotRegisteredDetectorTest : SystemUILintDetectorTest() {
pw.println("testDump");
}
}
- """.trimIndent()
+ """
+ .trimIndent()
),
*stubs,
)
@@ -127,7 +129,8 @@ class DumpableNotRegisteredDetectorTest : SystemUILintDetectorTest() {
pw.println("testDump");
}
}
- """.trimIndent()
+ """
+ .trimIndent()
),
*stubs,
)
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
index ed3d14a1f33f..bb34d917d326 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
@@ -21,7 +21,6 @@ import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class NonInjectedMainThreadDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = NonInjectedMainThreadDetector()
@@ -46,7 +45,7 @@ class NonInjectedMainThreadDetectorTest : SystemUILintDetectorTest() {
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedMainThreadDetector.ISSUE)
.run()
@@ -79,7 +78,7 @@ class NonInjectedMainThreadDetectorTest : SystemUILintDetectorTest() {
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedMainThreadDetector.ISSUE)
.run()
@@ -104,7 +103,7 @@ class NonInjectedMainThreadDetectorTest : SystemUILintDetectorTest() {
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedMainThreadDetector.ISSUE)
.run()
@@ -136,7 +135,7 @@ class NonInjectedMainThreadDetectorTest : SystemUILintDetectorTest() {
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedMainThreadDetector.ISSUE)
.run()
@@ -149,6 +148,4 @@ class NonInjectedMainThreadDetectorTest : SystemUILintDetectorTest() {
"""
)
}
-
- private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
index 846129aa12c1..fe5b576c5d21 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
@@ -21,7 +21,6 @@ import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class NonInjectedServiceDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = NonInjectedServiceDetector()
@@ -44,7 +43,7 @@ class NonInjectedServiceDetectorTest : SystemUILintDetectorTest() {
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedServiceDetector.ISSUE)
.run()
@@ -76,7 +75,7 @@ class NonInjectedServiceDetectorTest : SystemUILintDetectorTest() {
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedServiceDetector.ISSUE)
.run()
@@ -109,7 +108,7 @@ class NonInjectedServiceDetectorTest : SystemUILintDetectorTest() {
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedServiceDetector.ISSUE)
.run()
@@ -134,7 +133,7 @@ class NonInjectedServiceDetectorTest : SystemUILintDetectorTest() {
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(NonInjectedServiceDetector.ISSUE)
.run()
@@ -147,6 +146,4 @@ class NonInjectedServiceDetectorTest : SystemUILintDetectorTest() {
"""
)
}
-
- private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
index 0ac8f8e7c672..3f12569dfc61 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
@@ -21,7 +21,6 @@ import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class RegisterReceiverViaContextDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = RegisterReceiverViaContextDetector()
@@ -35,9 +34,8 @@ class RegisterReceiverViaContextDetectorTest : SystemUILintDetectorTest() {
TestFiles.java(
"""
package test.pkg;
- import android.content.BroadcastReceiver;
+
import android.content.Context;
- import android.content.IntentFilter;
public class TestClass {
public void bind(Context context, BroadcastReceiver receiver,
@@ -48,13 +46,13 @@ class RegisterReceiverViaContextDetectorTest : SystemUILintDetectorTest() {
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(RegisterReceiverViaContextDetector.ISSUE)
.run()
.expect(
"""
- src/test/pkg/TestClass.java:9: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
+ src/test/pkg/TestClass.java:8: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
context.registerReceiver(receiver, filter, 0);
~~~~~~~~~~~~~~~~
0 errors, 1 warnings
@@ -69,9 +67,8 @@ class RegisterReceiverViaContextDetectorTest : SystemUILintDetectorTest() {
TestFiles.java(
"""
package test.pkg;
- import android.content.BroadcastReceiver;
+
import android.content.Context;
- import android.content.IntentFilter;
@SuppressWarnings("RegisterReceiverViaContext")
public class TestClass {
@@ -83,7 +80,7 @@ class RegisterReceiverViaContextDetectorTest : SystemUILintDetectorTest() {
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(RegisterReceiverViaContextDetector.ISSUE)
.run()
@@ -97,11 +94,8 @@ class RegisterReceiverViaContextDetectorTest : SystemUILintDetectorTest() {
TestFiles.java(
"""
package test.pkg;
- import android.content.BroadcastReceiver;
+
import android.content.Context;
- import android.content.IntentFilter;
- import android.os.Handler;
- import android.os.UserHandle;
public class TestClass {
public void bind(Context context, BroadcastReceiver receiver,
@@ -113,13 +107,13 @@ class RegisterReceiverViaContextDetectorTest : SystemUILintDetectorTest() {
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(RegisterReceiverViaContextDetector.ISSUE)
.run()
.expect(
"""
- src/test/pkg/TestClass.java:11: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
+ src/test/pkg/TestClass.java:8: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
context.registerReceiverAsUser(receiver, UserHandle.ALL, filter,
~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
@@ -134,11 +128,8 @@ class RegisterReceiverViaContextDetectorTest : SystemUILintDetectorTest() {
TestFiles.java(
"""
package test.pkg;
- import android.content.BroadcastReceiver;
+
import android.content.Context;
- import android.content.IntentFilter;
- import android.os.Handler;
- import android.os.UserHandle;
public class TestClass {
public void bind(Context context, BroadcastReceiver receiver,
@@ -150,19 +141,17 @@ class RegisterReceiverViaContextDetectorTest : SystemUILintDetectorTest() {
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(RegisterReceiverViaContextDetector.ISSUE)
.run()
.expect(
"""
- src/test/pkg/TestClass.java:11: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
+ src/test/pkg/TestClass.java:8: Warning: Register BroadcastReceiver using BroadcastDispatcher instead of Context [RegisterReceiverViaContext]
context.registerReceiverForAllUsers(receiver, filter, "permission",
~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
"""
)
}
-
- private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
index 34a424918a79..7944ce3efe0f 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
@@ -21,7 +21,6 @@ import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class SlowUserQueryDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = SlowUserQueryDetector()
@@ -182,5 +181,19 @@ class SlowUserQueryDetectorTest : SystemUILintDetectorTest() {
.expectClean()
}
- private val stubs = androidStubs
+ private val stubs =
+ arrayOf(
+ *androidStubs,
+ java(
+ """
+package com.android.systemui.settings;
+import android.content.pm.UserInfo;
+public interface UserTracker {
+ int getUserId();
+ UserInfo getUserInfo();
+}
+"""
+ )
+ .indented()
+ )
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
index 34becc6a5b04..756751c79e24 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
@@ -21,7 +21,6 @@ import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class SoftwareBitmapDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = SoftwareBitmapDetector()
@@ -45,7 +44,7 @@ class SoftwareBitmapDetectorTest : SystemUILintDetectorTest() {
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(SoftwareBitmapDetector.ISSUE)
.run()
@@ -80,7 +79,7 @@ class SoftwareBitmapDetectorTest : SystemUILintDetectorTest() {
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(SoftwareBitmapDetector.ISSUE)
.run()
@@ -102,12 +101,10 @@ class SoftwareBitmapDetectorTest : SystemUILintDetectorTest() {
}
"""
),
- *stubs
+ *androidStubs
)
.issues(SoftwareBitmapDetector.ISSUE)
.run()
.expectClean()
}
-
- private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
index efe4c90ec44f..fd0001818d17 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
@@ -21,7 +21,6 @@ import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class StaticSettingsProviderDetectorTest : SystemUILintDetectorTest() {
override fun getDetector(): Detector = StaticSettingsProviderDetector()
@@ -85,7 +84,7 @@ class StaticSettingsProviderDetectorTest : SystemUILintDetectorTest() {
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(StaticSettingsProviderDetector.ISSUE)
.run()
@@ -226,12 +225,10 @@ class StaticSettingsProviderDetectorTest : SystemUILintDetectorTest() {
"""
)
.indented(),
- *stubs
+ *androidStubs
)
.issues(StaticSettingsProviderDetector.ISSUE)
.run()
.expectClean()
}
-
- private val stubs = androidStubs
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt
index 3f93f075fe8b..29b3828c5c4b 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SystemUILintDetectorTest.kt
@@ -10,7 +10,6 @@ import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.junit.runners.model.Statement
-@Suppress("UnstableApiUsage")
@RunWith(JUnit4::class)
abstract class SystemUILintDetectorTest : LintDetectorTest() {
@@ -18,7 +17,7 @@ abstract class SystemUILintDetectorTest : LintDetectorTest() {
@ClassRule
@JvmField
val libraryChecker: LibraryExists =
- LibraryExists("framework.jar", "androidx.annotation_annotation.jar")
+ LibraryExists("framework.jar", "androidx.annotation_annotation-nodeps.jar")
}
class LibraryExists(vararg val libraryNames: String) : TestRule {
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/TestFunctionNameViolationDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/TestFunctionNameViolationDetectorTest.kt
index db73154c02d9..a4e82a779de9 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/TestFunctionNameViolationDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/TestFunctionNameViolationDetectorTest.kt
@@ -18,28 +18,22 @@
package com.android.internal.systemui.lint
import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestFiles
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
import org.junit.Test
-@Suppress("UnstableApiUsage")
class TestFunctionNameViolationDetectorTest : SystemUILintDetectorTest() {
- override fun getDetector(): Detector {
- return TestFunctionNameViolationDetector()
- }
+ override fun getDetector(): Detector = TestFunctionNameViolationDetector()
- override fun getIssues(): List<Issue> {
- return listOf(
- TestFunctionNameViolationDetector.ISSUE,
- )
- }
+ override fun getIssues(): List<Issue> = listOf(TestFunctionNameViolationDetector.ISSUE)
@Test
fun violations() {
lint()
.files(
- kotlin(
- """
+ TestFiles.kotlin(
+ """
package test.pkg.name
import org.junit.Test
@@ -64,13 +58,11 @@ class TestFunctionNameViolationDetectorTest : SystemUILintDetectorTest() {
}
}
"""
- .trimIndent()
- ),
- testAnnotationStub,
- )
- .issues(
- TestFunctionNameViolationDetector.ISSUE,
+ )
+ .indented(),
+ testAnnotationStub
)
+ .issues(TestFunctionNameViolationDetector.ISSUE)
.run()
.expectWarningCount(0)
.expect(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 4533f58c1c37..356bfe23f6a5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -11,6 +11,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.dimensionResource
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.ElementKey
@@ -24,11 +25,10 @@ import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.observableTransitionState
import com.android.compose.animation.scene.transitions
-import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
-import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.communal.util.CommunalColors
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
import com.android.systemui.scene.ui.composable.SceneTransitionLayoutDataSource
@@ -75,6 +75,7 @@ fun CommunalContainer(
viewModel: CommunalViewModel,
dataSourceDelegator: SceneDataSourceDelegator,
dialogFactory: SystemUIDialogFactory,
+ colors: CommunalColors,
) {
val coroutineScope = rememberCoroutineScope()
val currentSceneKey: SceneKey by viewModel.currentScene.collectAsState(CommunalScenes.Blank)
@@ -135,7 +136,7 @@ fun CommunalContainer(
emptyMap()
},
) {
- CommunalScene(viewModel, dialogFactory, modifier = modifier)
+ CommunalScene(viewModel, colors, dialogFactory, modifier = modifier)
}
}
}
@@ -143,15 +144,18 @@ fun CommunalContainer(
/** Scene containing the glanceable hub UI. */
@Composable
private fun SceneScope.CommunalScene(
- viewModel: BaseCommunalViewModel,
+ viewModel: CommunalViewModel,
+ colors: CommunalColors,
dialogFactory: SystemUIDialogFactory,
modifier: Modifier = Modifier,
) {
+ val backgroundColor by colors.backgroundColor.collectAsState()
+
Box(
modifier =
Modifier.element(Communal.Elements.Scrim)
.fillMaxSize()
- .background(LocalAndroidColorScheme.current.outlineVariant),
+ .background(Color(backgroundColor.toArgb())),
)
Box(modifier.element(Communal.Elements.Content)) {
CommunalHub(viewModel = viewModel, dialogFactory = dialogFactory)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 5d87a03fdba7..ddfb5f6adaec 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -250,7 +250,6 @@ fun CommunalHub(
)
isDraggingToRemove
},
- onOpenWidgetPicker = onOpenWidgetPicker,
gridState = gridState,
contentListState = contentListState,
selectedKey = selectedKey,
@@ -393,7 +392,6 @@ private fun BoxScope.CommunalHubLazyGrid(
contentListState: ContentListState,
setGridCoordinates: (coordinates: LayoutCoordinates) -> Unit,
updateDragPositionForRemove: (offset: Offset) -> Boolean,
- onOpenWidgetPicker: (() -> Unit)? = null,
widgetConfigurator: WidgetConfigurator?,
) {
var gridModifier =
@@ -461,7 +459,6 @@ private fun BoxScope.CommunalHubLazyGrid(
model = list[index],
viewModel = viewModel,
size = size,
- onOpenWidgetPicker = onOpenWidgetPicker,
selected = selected && !isDragging,
widgetConfigurator = widgetConfigurator,
)
@@ -739,7 +736,6 @@ private fun CommunalContent(
size: SizeF,
selected: Boolean,
modifier: Modifier = Modifier,
- onOpenWidgetPicker: (() -> Unit)? = null,
widgetConfigurator: WidgetConfigurator? = null,
) {
when (model) {
@@ -1145,7 +1141,7 @@ object Dimensions {
val CardHeightHalf = 282.dp
val CardHeightThird = 177.33.dp
val CardOutlineWidth = 3.dp
- val GridTopSpacing = 72.dp
+ val GridTopSpacing = 64.dp
val GridHeight = CardHeightFull + GridTopSpacing
val Spacing = 16.dp
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
index dee255917359..37fe7989cead 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
@@ -161,9 +161,9 @@ internal class DragAndDropTargetState(
private var isOnRemoveButton = false
fun onStarted() {
- // assume item will be added to the second to last position before CTA tile.
+ // assume item will be added to the end.
+ contentListState.list.add(placeHolder)
placeHolderIndex = contentListState.list.size - 1
- placeHolderIndex?.let { contentListState.list.add(it, placeHolder) }
}
fun onMoved(event: DragAndDropEvent) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index 28e92aad914a..e499c6908b86 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -26,9 +26,11 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.IntRect
import com.android.compose.animation.scene.SceneScope
+import com.android.compose.modifiers.padding
import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
@@ -43,6 +45,7 @@ import dagger.Module
import dagger.multibindings.IntoSet
import java.util.Optional
import javax.inject.Inject
+import kotlin.math.roundToInt
/**
* Renders the lockscreen scene when showing with the default layout (e.g. vertical phone form
@@ -68,6 +71,7 @@ constructor(
val isUdfpsVisible = viewModel.isUdfpsVisible
val shouldUseSplitNotificationShade by
viewModel.shouldUseSplitNotificationShade.collectAsState()
+ val unfoldTranslations by viewModel.unfoldTranslations.collectAsState()
LockscreenLongPress(
viewModel = viewModel.longPress,
@@ -79,10 +83,25 @@ constructor(
Column(
modifier = Modifier.fillMaxSize(),
) {
- with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+ with(statusBarSection) {
+ StatusBar(
+ modifier =
+ Modifier.fillMaxWidth()
+ .padding(
+ horizontal = { unfoldTranslations.start.roundToInt() },
+ )
+ )
+ }
Box {
- with(topAreaSection) { DefaultClockLayout() }
+ with(topAreaSection) {
+ DefaultClockLayout(
+ modifier =
+ Modifier.graphicsLayer {
+ translationX = unfoldTranslations.start
+ }
+ )
+ }
if (shouldUseSplitNotificationShade) {
with(notificationSection) {
Notifications(
@@ -127,8 +146,18 @@ constructor(
// Aligned to bottom and NOT constrained by the lock icon.
with(bottomAreaSection) {
- Shortcut(isStart = true, applyPadding = true)
- Shortcut(isStart = false, applyPadding = true)
+ Shortcut(
+ isStart = true,
+ applyPadding = true,
+ modifier =
+ Modifier.graphicsLayer { translationX = unfoldTranslations.start },
+ )
+ Shortcut(
+ isStart = false,
+ applyPadding = true,
+ modifier =
+ Modifier.graphicsLayer { translationX = unfoldTranslations.end },
+ )
}
with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) }
},
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index b8f00dce53df..9d31955122eb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -26,9 +26,11 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.IntRect
import com.android.compose.animation.scene.SceneScope
+import com.android.compose.modifiers.padding
import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
@@ -43,6 +45,7 @@ import dagger.Module
import dagger.multibindings.IntoSet
import java.util.Optional
import javax.inject.Inject
+import kotlin.math.roundToInt
/**
* Renders the lockscreen scene when showing with the default layout (e.g. vertical phone form
@@ -68,6 +71,7 @@ constructor(
val isUdfpsVisible = viewModel.isUdfpsVisible
val shouldUseSplitNotificationShade by
viewModel.shouldUseSplitNotificationShade.collectAsState()
+ val unfoldTranslations by viewModel.unfoldTranslations.collectAsState()
LockscreenLongPress(
viewModel = viewModel.longPress,
@@ -79,10 +83,25 @@ constructor(
Column(
modifier = Modifier.fillMaxSize(),
) {
- with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+ with(statusBarSection) {
+ StatusBar(
+ modifier =
+ Modifier.fillMaxWidth()
+ .padding(
+ horizontal = { unfoldTranslations.start.roundToInt() },
+ )
+ )
+ }
Box {
- with(topAreaSection) { DefaultClockLayout() }
+ with(topAreaSection) {
+ DefaultClockLayout(
+ modifier =
+ Modifier.graphicsLayer {
+ translationX = unfoldTranslations.start
+ },
+ )
+ }
if (shouldUseSplitNotificationShade) {
with(notificationSection) {
Notifications(
@@ -111,12 +130,26 @@ constructor(
}
// Constrained to the left of the lock icon (in left-to-right layouts).
- with(bottomAreaSection) { Shortcut(isStart = true, applyPadding = false) }
+ with(bottomAreaSection) {
+ Shortcut(
+ isStart = true,
+ applyPadding = false,
+ modifier =
+ Modifier.graphicsLayer { translationX = unfoldTranslations.start },
+ )
+ }
with(lockSection) { LockIcon() }
// Constrained to the right of the lock icon (in left-to-right layouts).
- with(bottomAreaSection) { Shortcut(isStart = false, applyPadding = false) }
+ with(bottomAreaSection) {
+ Shortcut(
+ isStart = false,
+ applyPadding = false,
+ modifier =
+ Modifier.graphicsLayer { translationX = unfoldTranslations.end },
+ )
+ }
// Aligned to bottom and constrained to below the lock icon.
Column(modifier = Modifier.fillMaxWidth()) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt
index 2a99039fa306..238a230ff013 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt
@@ -19,6 +19,8 @@ package com.android.systemui.keyguard.ui.composable.modifier
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.boundsInWindow
@@ -39,9 +41,12 @@ fun Modifier.burnInAware(
params: BurnInParameters,
isClock: Boolean = false,
): Modifier {
- val burnIn = viewModel.movement(params)
+ val translationYState = remember { mutableStateOf(0F) }
+ val copiedParams = params.copy(translationY = { translationYState.value })
+ val burnIn = viewModel.movement(copiedParams)
val translationX by burnIn.map { it.translationX.toFloat() }.collectAsState(initial = 0f)
val translationY by burnIn.map { it.translationY.toFloat() }.collectAsState(initial = 0f)
+ translationYState.value = translationY
val scaleViewModel by
burnIn
.map {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index dc3b612d3594..418c6bb9af70 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -44,10 +44,27 @@ internal class Scene(
internal val scope = SceneScopeImpl(layoutImpl, this)
var content by mutableStateOf(content)
- var userActions by mutableStateOf(actions)
+ private var _userActions by mutableStateOf(checkValid(actions))
var zIndex by mutableFloatStateOf(zIndex)
var targetSize by mutableStateOf(IntSize.Zero)
+ var userActions
+ get() = _userActions
+ set(value) {
+ _userActions = checkValid(value)
+ }
+
+ private fun checkValid(
+ userActions: Map<UserAction, UserActionResult>
+ ): Map<UserAction, UserActionResult> {
+ userActions.forEach { (action, result) ->
+ if (key == result.toScene) {
+ error("Transition to the same scene is not supported. Scene $key, action $action")
+ }
+ }
+ return userActions
+ }
+
@Composable
@OptIn(ExperimentalComposeUiApi::class)
fun Content(modifier: Modifier = Modifier) {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index 723a1825f205..2eaccb477524 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -48,10 +48,14 @@ import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestScenes.SceneA
+import com.android.compose.animation.scene.TestScenes.SceneB
+import com.android.compose.animation.scene.TestScenes.SceneC
import com.android.compose.test.assertSizeIsEqualTo
import com.android.compose.test.subjects.DpOffsetSubject
import com.android.compose.test.subjects.assertThat
import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -62,7 +66,7 @@ class SceneTransitionLayoutTest {
private val LayoutSize = 300.dp
}
- private var currentScene by mutableStateOf(TestScenes.SceneA)
+ private var currentScene by mutableStateOf(SceneA)
private lateinit var layoutState: SceneTransitionLayoutState
// We use createAndroidComposeRule() here and not createComposeRule() because we need an
@@ -84,15 +88,15 @@ class SceneTransitionLayoutTest {
modifier = Modifier.size(LayoutSize),
) {
scene(
- TestScenes.SceneA,
- userActions = mapOf(Back to TestScenes.SceneB),
+ SceneA,
+ userActions = mapOf(Back to SceneB),
) {
Box(Modifier.fillMaxSize()) {
SharedFoo(size = 50.dp, childOffset = 0.dp, Modifier.align(Alignment.TopEnd))
Text("SceneA")
}
}
- scene(TestScenes.SceneB) {
+ scene(SceneB) {
Box(Modifier.fillMaxSize()) {
SharedFoo(
size = 100.dp,
@@ -102,7 +106,7 @@ class SceneTransitionLayoutTest {
Text("SceneB")
}
}
- scene(TestScenes.SceneC) {
+ scene(SceneC) {
Box(Modifier.fillMaxSize()) {
SharedFoo(
size = 150.dp,
@@ -144,42 +148,42 @@ class SceneTransitionLayoutTest {
rule.onNodeWithText("SceneB").assertDoesNotExist()
rule.onNodeWithText("SceneC").assertDoesNotExist()
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
// Change to scene B. Only that scene is displayed.
- currentScene = TestScenes.SceneB
+ currentScene = SceneB
rule.onNodeWithText("SceneA").assertDoesNotExist()
rule.onNodeWithText("SceneB").assertIsDisplayed()
rule.onNodeWithText("SceneC").assertDoesNotExist()
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneB)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneB)
}
@Test
fun testBack() {
rule.setContent { TestContent() }
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
rule.activity.onBackPressed()
rule.waitForIdle()
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneB)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneB)
}
@Test
fun testTransitionState() {
rule.setContent { TestContent() }
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
// We will advance the clock manually.
rule.mainClock.autoAdvance = false
// Change the current scene. Until composition is triggered, this won't change the layout
// state.
- currentScene = TestScenes.SceneB
+ currentScene = SceneB
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
// On the next frame, we will recompose because currentScene changed, which will start the
// transition (i.e. it will change the transitionState to be a Transition) in a
@@ -187,8 +191,8 @@ class SceneTransitionLayoutTest {
rule.mainClock.advanceTimeByFrame()
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Transition::class.java)
val transition = layoutState.transitionState as TransitionState.Transition
- assertThat(transition.fromScene).isEqualTo(TestScenes.SceneA)
- assertThat(transition.toScene).isEqualTo(TestScenes.SceneB)
+ assertThat(transition.fromScene).isEqualTo(SceneA)
+ assertThat(transition.toScene).isEqualTo(SceneB)
assertThat(transition.progress).isEqualTo(0f)
// Then, on the next frame, the animator we started gets its initial value and clock
@@ -216,7 +220,7 @@ class SceneTransitionLayoutTest {
// B.
rule.mainClock.advanceTimeByFrame()
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneB)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneB)
}
@Test
@@ -242,7 +246,7 @@ class SceneTransitionLayoutTest {
// Go to scene B and let the animation start. See [testLayoutState()] and
// [androidx.compose.ui.test.MainTestClock] to understand why we need to advance the clock
// by 2 frames to be at the start of the animation.
- currentScene = TestScenes.SceneB
+ currentScene = SceneB
rule.mainClock.advanceTimeByFrame()
rule.mainClock.advanceTimeByFrame()
@@ -251,7 +255,7 @@ class SceneTransitionLayoutTest {
// Foo is shared between Scene A and Scene B, and is therefore placed/drawn in Scene B given
// that B has a higher zIndex than A.
- sharedFoo = rule.onNode(isElement(TestElements.Foo, TestScenes.SceneB))
+ sharedFoo = rule.onNode(isElement(TestElements.Foo, SceneB))
// In scene B, foo is at the top start (x = 0, y = 0) of the layout and has a size of
// 100.dp. We pause at the middle of the transition, so it should now be 75.dp given that we
@@ -273,7 +277,7 @@ class SceneTransitionLayoutTest {
.of(DpOffset(25.dp, 25.dp))
// Animate to scene C, let the animation start then go to the middle of the transition.
- currentScene = TestScenes.SceneC
+ currentScene = SceneC
rule.mainClock.advanceTimeByFrame()
rule.mainClock.advanceTimeByFrame()
rule.mainClock.advanceTimeBy(TestTransitionDuration / 2)
@@ -285,7 +289,7 @@ class SceneTransitionLayoutTest {
val expectedLeft = 0.dp
val expectedSize = 100.dp + (150.dp - 100.dp) * interpolatedProgress
- sharedFoo = rule.onNode(isElement(TestElements.Foo, TestScenes.SceneC))
+ sharedFoo = rule.onNode(isElement(TestElements.Foo, SceneC))
assertThat((layoutState.transitionState as TransitionState.Transition).progress)
.isEqualTo(interpolatedProgress)
sharedFoo.assertWidthIsEqualTo(expectedSize)
@@ -302,15 +306,15 @@ class SceneTransitionLayoutTest {
// Wait for the transition to C to finish.
rule.mainClock.advanceTimeBy(TestTransitionDuration)
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneC)
// Go back to scene A. This should happen instantly (once the animation started, i.e. after
// 2 frames) given that we use a snap() animation spec.
- currentScene = TestScenes.SceneA
+ currentScene = SceneA
rule.mainClock.advanceTimeByFrame()
rule.mainClock.advanceTimeByFrame()
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
- assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+ assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA)
}
@Test
@@ -346,4 +350,28 @@ class SceneTransitionLayoutTest {
)
}
}
+
+ @Test
+ fun userActionFromSceneAToSceneA_throwsNotSupported() {
+ val exception: IllegalStateException =
+ assertThrows(IllegalStateException::class.java) {
+ rule.setContent {
+ SceneTransitionLayout(
+ state =
+ updateSceneTransitionLayoutState(
+ currentScene = currentScene,
+ onChangeScene = { currentScene = it },
+ transitions = EmptyTestTransitions
+ ),
+ modifier = Modifier.size(LayoutSize),
+ ) {
+ // from SceneA to SceneA
+ scene(SceneA, userActions = mapOf(Back to SceneA), content = {})
+ }
+ }
+ }
+
+ assertThat(exception).hasMessageThat().contains(Back.toString())
+ assertThat(exception).hasMessageThat().contains(SceneA.debugName)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
index 3889703e74c4..e332656e0f15 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
@@ -18,9 +18,6 @@ package com.android.systemui.haptics.qs
import android.os.VibrationEffect
import android.testing.TestableLooper.RunWithLooper
-import android.view.MotionEvent
-import android.view.View
-import androidx.test.core.view.MotionEventBuilder
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -29,18 +26,15 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.haptics.vibratorHelper
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
-import com.android.systemui.kosmos.backgroundCoroutineContext
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
@@ -50,7 +44,6 @@ import org.mockito.junit.MockitoRule
class QSLongPressEffectTest : SysuiTestCase() {
@Rule @JvmField val mMockitoRule: MockitoRule = MockitoJUnit.rule()
- @Mock private lateinit var testView: View
@get:Rule val animatorTestRule = AnimatorTestRule(this)
private val kosmos = testKosmos()
private val vibratorHelper = kosmos.vibratorHelper
@@ -73,7 +66,6 @@ class QSLongPressEffectTest : SysuiTestCase() {
QSLongPressEffect(
vibratorHelper,
kosmos.keyguardInteractor,
- CoroutineScope(kosmos.backgroundCoroutineContext),
)
longPressEffect.initializeEffect(effectDuration)
}
@@ -133,8 +125,7 @@ class QSLongPressEffectTest : SysuiTestCase() {
@Test
fun onActionDown_whileIdle_startsWait() = testWithScope {
// GIVEN an action down event occurs
- val downEvent = buildMotionEvent(MotionEvent.ACTION_DOWN)
- longPressEffect.onTouch(testView, downEvent)
+ longPressEffect.handleActionDown()
// THEN the effect moves to the TIMEOUT_WAIT state
val state by collectLastValue(longPressEffect.state)
@@ -144,8 +135,7 @@ class QSLongPressEffectTest : SysuiTestCase() {
@Test
fun onActionCancel_whileWaiting_goesIdle() = testWhileWaiting {
// GIVEN an action cancel occurs
- val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
- longPressEffect.onTouch(testView, cancelEvent)
+ longPressEffect.handleActionCancel()
// THEN the effect goes back to idle and does not start
val state by collectLastValue(longPressEffect.state)
@@ -159,8 +149,7 @@ class QSLongPressEffectTest : SysuiTestCase() {
val action by collectLastValue(longPressEffect.actionType)
// GIVEN an action up occurs
- val upEvent = buildMotionEvent(MotionEvent.ACTION_UP)
- longPressEffect.onTouch(testView, upEvent)
+ longPressEffect.handleActionUp()
// THEN the action to invoke is the click action and the effect does not start
assertThat(action).isEqualTo(QSLongPressEffect.ActionType.CLICK)
@@ -182,8 +171,7 @@ class QSLongPressEffectTest : SysuiTestCase() {
animatorTestRule.advanceTimeBy(effectDuration / 2L)
// WHEN an action up occurs
- val upEvent = buildMotionEvent(MotionEvent.ACTION_UP)
- longPressEffect.onTouch(testView, upEvent)
+ longPressEffect.handleActionUp()
// THEN the effect gets reversed at 50% progress
assertEffectReverses(0.5f)
@@ -195,8 +183,7 @@ class QSLongPressEffectTest : SysuiTestCase() {
animatorTestRule.advanceTimeBy(effectDuration / 2L)
// WHEN an action cancel occurs
- val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
- longPressEffect.onTouch(testView, cancelEvent)
+ longPressEffect.handleActionCancel()
// THEN the effect gets reversed at 50% progress
assertEffectReverses(0.5f)
@@ -230,12 +217,10 @@ class QSLongPressEffectTest : SysuiTestCase() {
animatorTestRule.advanceTimeBy(effectDuration / 2L)
// GIVEN an action cancel occurs and the effect gets reversed
- val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
- longPressEffect.onTouch(testView, cancelEvent)
+ longPressEffect.handleActionCancel()
// GIVEN an action down occurs
- val downEvent = buildMotionEvent(MotionEvent.ACTION_DOWN)
- longPressEffect.onTouch(testView, downEvent)
+ longPressEffect.handleActionDown()
// THEN the effect resets
assertEffectResets()
@@ -247,8 +232,7 @@ class QSLongPressEffectTest : SysuiTestCase() {
animatorTestRule.advanceTimeBy(effectDuration / 2L)
// GIVEN an action cancel occurs and the effect gets reversed
- val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
- longPressEffect.onTouch(testView, cancelEvent)
+ longPressEffect.handleActionCancel()
// GIVEN that the animation completes after a sufficient amount of time
animatorTestRule.advanceTimeBy(effectDuration.toLong())
@@ -258,9 +242,6 @@ class QSLongPressEffectTest : SysuiTestCase() {
assertThat(state).isEqualTo(QSLongPressEffect.State.IDLE)
}
- private fun buildMotionEvent(action: Int): MotionEvent =
- MotionEventBuilder.newBuilder().setAction(action).build()
-
private fun testWithScope(test: suspend TestScope.() -> Unit) =
with(kosmos) { testScope.runTest { test() } }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
index e9a825721f5f..3497183c2c69 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
@@ -21,17 +21,21 @@ import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardClockSwitch
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.authController
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.testKosmos
+import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import java.util.Locale
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -137,4 +141,47 @@ class LockscreenContentViewModelTest : SysuiTestCase() {
.isFalse()
}
}
+
+ @Test
+ fun unfoldTranslations() =
+ with(kosmos) {
+ testScope.runTest {
+ val maxTranslation = prepareConfiguration()
+ val translations by collectLastValue(underTest.unfoldTranslations)
+
+ val unfoldProvider = fakeUnfoldTransitionProgressProvider
+ unfoldProvider.onTransitionStarted()
+ assertThat(translations?.start).isEqualTo(0f)
+ assertThat(translations?.end).isEqualTo(-0f)
+
+ repeat(10) { repetition ->
+ val transitionProgress = 0.1f * (repetition + 1)
+ unfoldProvider.onTransitionProgress(transitionProgress)
+ assertThat(translations?.start)
+ .isEqualTo((1 - transitionProgress) * maxTranslation)
+ assertThat(translations?.end)
+ .isEqualTo(-(1 - transitionProgress) * maxTranslation)
+ }
+
+ unfoldProvider.onTransitionFinishing()
+ assertThat(translations?.start).isEqualTo(0f)
+ assertThat(translations?.end).isEqualTo(-0f)
+
+ unfoldProvider.onTransitionFinished()
+ assertThat(translations?.start).isEqualTo(0f)
+ assertThat(translations?.end).isEqualTo(-0f)
+ }
+ }
+
+ private fun prepareConfiguration(): Int {
+ val configuration = context.resources.configuration
+ configuration.setLayoutDirection(Locale.US)
+ kosmos.fakeConfigurationRepository.onConfigurationChange(configuration)
+ val maxTranslation = 10
+ kosmos.fakeConfigurationRepository.setDimensionPixelSize(
+ R.dimen.notification_side_paddings,
+ maxTranslation,
+ )
+ return maxTranslation
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
index f0498ded64a5..1501d9c3505a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
@@ -51,7 +51,6 @@ class ActivityStarterImplTest : SysuiTestCase() {
statusBarStateController = statusBarStateController,
mainExecutor = mainExecutor,
legacyActivityStarter = { legacyActivityStarterInternal },
- activityStarterInternal = { activityStarterInternal },
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
index 106b54891948..97c8d5fd9e4a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryHelper.ACTIVITY_DEFAULT
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import kotlinx.coroutines.flow.MutableStateFlow
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
index d06a6e26b4ce..a6fdd0391787 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
@@ -16,24 +16,28 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository
-import android.net.ConnectivityManager
import android.net.wifi.WifiManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.demomode.DemoMode
import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.log.LogBuffer
import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
-import com.android.systemui.statusbar.pipeline.wifi.shared.WifiInputLogger
import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
+import com.android.wifitrackerlib.WifiPickerTracker
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -59,14 +63,21 @@ class WifiRepositorySwitcherTest : SysuiTestCase() {
private lateinit var demoImpl: DemoWifiRepository
@Mock private lateinit var demoModeController: DemoModeController
- @Mock private lateinit var logger: WifiInputLogger
@Mock private lateinit var tableLogger: TableLogBuffer
- @Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var wifiManager: WifiManager
@Mock private lateinit var demoModeWifiDataSource: DemoModeWifiDataSource
+ @Mock private lateinit var wifiPickerTrackerFactory: WifiPickerTrackerFactory
+ @Mock private lateinit var wifiPickerTracker: WifiPickerTracker
+
+ private val wifiLogBuffer = LogBuffer("wifi", maxSize = 100, logcatEchoTracker = mock())
private val demoModelFlow = MutableStateFlow<FakeWifiEventModel?>(null)
private val mainExecutor = FakeExecutor(FakeSystemClock())
+ private val featureFlags =
+ FakeFeatureFlagsClassic().also {
+ it.set(Flags.INSTANT_TETHER, true)
+ it.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+ }
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -78,17 +89,18 @@ class WifiRepositorySwitcherTest : SysuiTestCase() {
// Never start in demo mode
whenever(demoModeController.isInDemoMode).thenReturn(false)
+ whenever(wifiPickerTrackerFactory.create(any(), any(), any())).thenReturn(wifiPickerTracker)
+
realImpl =
WifiRepositoryImpl(
- fakeBroadcastDispatcher,
- connectivityManager,
- FakeConnectivityRepository(),
- logger,
- tableLogger,
+ featureFlags,
+ testScope.backgroundScope,
mainExecutor,
testDispatcher,
- testScope.backgroundScope,
+ wifiPickerTrackerFactory,
wifiManager,
+ wifiLogBuffer,
+ tableLogger,
)
whenever(demoModeWifiDataSource.wifiEvents).thenReturn(demoModelFlow)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
deleted file mode 100644
index cf20ba87e8c2..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ /dev/null
@@ -1,1323 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod
-
-import android.content.Intent
-import android.net.ConnectivityManager
-import android.net.Network
-import android.net.NetworkCapabilities
-import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
-import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
-import android.net.NetworkCapabilities.TRANSPORT_VPN
-import android.net.NetworkCapabilities.TRANSPORT_WIFI
-import android.net.TransportInfo
-import android.net.VpnTransportInfo
-import android.net.vcn.VcnTransportInfo
-import android.net.wifi.ScanResult
-import android.net.wifi.WifiInfo
-import android.net.wifi.WifiManager
-import android.net.wifi.WifiManager.TrafficStateCallback
-import android.net.wifi.WifiManager.UNKNOWN_SSID
-import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlots
-import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
-import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
-import com.android.systemui.statusbar.pipeline.wifi.shared.WifiInputLogger
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import java.util.concurrent.Executor
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-/**
- * Note: Any new tests added here may also need to be added to [WifiRepositoryViaTrackerLibTest].
- */
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class WifiRepositoryImplTest : SysuiTestCase() {
-
- private lateinit var underTest: WifiRepositoryImpl
-
- @Mock private lateinit var logger: WifiInputLogger
- @Mock private lateinit var tableLogger: TableLogBuffer
- @Mock private lateinit var connectivityManager: ConnectivityManager
- @Mock private lateinit var wifiManager: WifiManager
- private lateinit var executor: Executor
- private lateinit var connectivityRepository: ConnectivityRepository
-
- private val dispatcher = StandardTestDispatcher()
- private val testScope = TestScope(dispatcher)
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- executor = FakeExecutor(FakeSystemClock())
-
- connectivityRepository =
- ConnectivityRepositoryImpl(
- connectivityManager,
- ConnectivitySlots(context),
- context,
- mock(),
- mock(),
- testScope.backgroundScope,
- mock(),
- )
-
- underTest = createRepo()
- }
-
- @Test
- fun isWifiEnabled_initiallyGetsWifiManagerValue() =
- testScope.runTest {
- whenever(wifiManager.isWifiEnabled).thenReturn(true)
-
- underTest = createRepo()
- testScope.runCurrent()
-
- assertThat(underTest.isWifiEnabled.value).isTrue()
- }
-
- @Test
- fun isWifiEnabled_networkCapabilitiesChanged_valueUpdated() =
- testScope.runTest {
- collectLastValue(underTest.wifiNetwork)
- val latest by collectLastValue(underTest.isWifiEnabled)
-
- whenever(wifiManager.isWifiEnabled).thenReturn(true)
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
-
- assertThat(latest).isTrue()
-
- whenever(wifiManager.isWifiEnabled).thenReturn(false)
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
-
- assertThat(latest).isFalse()
- }
-
- @Test
- fun isWifiEnabled_networkLost_valueUpdated() =
- testScope.runTest {
- collectLastValue(underTest.wifiNetwork)
- val latest by collectLastValue(underTest.isWifiEnabled)
-
- whenever(wifiManager.isWifiEnabled).thenReturn(true)
- getNetworkCallback().onLost(NETWORK)
-
- assertThat(latest).isTrue()
-
- whenever(wifiManager.isWifiEnabled).thenReturn(false)
- getNetworkCallback().onLost(NETWORK)
-
- assertThat(latest).isFalse()
- }
-
- @Test
- fun isWifiEnabled_intentsReceived_valueUpdated() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiEnabled)
-
- whenever(wifiManager.isWifiEnabled).thenReturn(true)
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
- context,
- Intent(WifiManager.WIFI_STATE_CHANGED_ACTION),
- )
-
- assertThat(latest).isTrue()
-
- whenever(wifiManager.isWifiEnabled).thenReturn(false)
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
- context,
- Intent(WifiManager.WIFI_STATE_CHANGED_ACTION),
- )
-
- assertThat(latest).isFalse()
- }
-
- @Test
- fun isWifiEnabled_bothIntentAndNetworkUpdates_valueAlwaysUpdated() =
- testScope.runTest {
- collectLastValue(underTest.wifiNetwork)
- val latest by collectLastValue(underTest.isWifiEnabled)
-
- whenever(wifiManager.isWifiEnabled).thenReturn(false)
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
- context,
- Intent(WifiManager.WIFI_STATE_CHANGED_ACTION),
- )
- assertThat(latest).isFalse()
-
- whenever(wifiManager.isWifiEnabled).thenReturn(true)
- getNetworkCallback().onLost(NETWORK)
- assertThat(latest).isTrue()
-
- whenever(wifiManager.isWifiEnabled).thenReturn(false)
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
- assertThat(latest).isFalse()
-
- whenever(wifiManager.isWifiEnabled).thenReturn(true)
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
- context,
- Intent(WifiManager.WIFI_STATE_CHANGED_ACTION),
- )
- assertThat(latest).isTrue()
- }
-
- @Test
- fun isWifiDefault_initiallyGetsDefault() =
- testScope.runTest { assertThat(underTest.isWifiDefault.value).isFalse() }
-
- @Test
- fun isWifiDefault_wifiNetwork_isTrue() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val wifiInfo = mock<WifiInfo>().apply { whenever(this.ssid).thenReturn(SSID) }
-
- getDefaultNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
-
- assertThat(latest).isTrue()
- }
-
- /** Regression test for b/266628069. */
- @Test
- fun isWifiDefault_transportInfoIsNotWifi_andNoWifiTransport_false() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val transportInfo =
- VpnTransportInfo(
- /* type= */ 0,
- /* sessionId= */ "sessionId",
- )
- val networkCapabilities =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_VPN)).thenReturn(true)
- whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
- whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
- whenever(it.transportInfo).thenReturn(transportInfo)
- }
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, networkCapabilities)
-
- assertThat(latest).isFalse()
- }
-
- /** Regression test for b/266628069. */
- @Test
- fun isWifiDefault_transportInfoIsNotWifi_butHasWifiTransport_true() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val transportInfo =
- VpnTransportInfo(
- /* type= */ 0,
- /* sessionId= */ "sessionId",
- )
- val networkCapabilities =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_VPN)).thenReturn(true)
- whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
- whenever(it.transportInfo).thenReturn(transportInfo)
- }
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, networkCapabilities)
-
- assertThat(latest).isTrue()
- }
-
- @Test
- fun isWifiDefault_carrierMergedViaCellular_isTrue() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val carrierMergedInfo =
- mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
-
- val capabilities =
- mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
- whenever(this.transportInfo).thenReturn(carrierMergedInfo)
- }
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest).isTrue()
- }
-
- @Test
- fun isWifiDefault_carrierMergedViaCellular_withVcnTransport_isTrue() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val capabilities =
- mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
- whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
- }
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest).isTrue()
- }
-
- @Test
- fun isWifiDefault_carrierMergedViaWifi_isTrue() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val carrierMergedInfo =
- mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
-
- val capabilities =
- mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
- whenever(this.transportInfo).thenReturn(carrierMergedInfo)
- }
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest).isTrue()
- }
-
- @Test
- fun isWifiDefault_carrierMergedViaWifi_withVcnTransport_isTrue() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val capabilities =
- mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
- whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
- }
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest).isTrue()
- }
-
- @Test
- fun isWifiDefault_cellularAndWifiTransports_usesCellular_isTrue() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val capabilities =
- mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
- }
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest).isTrue()
- }
-
- @Test
- fun isWifiDefault_cellularNotVcnNetwork_isFalse() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val capabilities =
- mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(this.transportInfo).thenReturn(mock())
- }
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest).isFalse()
- }
-
- @Test
- fun isWifiDefault_isCarrierMergedViaUnderlyingWifi_isTrue() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val underlyingNetwork = mock<Network>()
- val carrierMergedInfo =
- mock<WifiInfo>().apply {
- mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
- }
- val underlyingWifiCapabilities =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(carrierMergedInfo)
- }
- whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
- .thenReturn(underlyingWifiCapabilities)
-
- // WHEN the main capabilities have an underlying carrier merged network via WIFI
- // transport and WifiInfo
- val mainCapabilities =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(null)
- whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
- }
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
-
- // THEN the wifi network is carrier merged, so wifi is default
- assertThat(latest).isTrue()
- }
-
- @Test
- fun isWifiDefault_isCarrierMergedViaUnderlyingCellular_isTrue() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- val underlyingCarrierMergedNetwork = mock<Network>()
- val carrierMergedInfo =
- mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
- val underlyingCapabilities =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
- }
- whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
- .thenReturn(underlyingCapabilities)
-
- // WHEN the main capabilities have an underlying carrier merged network via CELLULAR
- // transport and VcnTransportInfo
- val mainCapabilities =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(null)
- whenever(it.underlyingNetworks)
- .thenReturn(listOf(underlyingCarrierMergedNetwork))
- }
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
-
- // THEN the wifi network is carrier merged, so wifi is default
- assertThat(latest).isTrue()
- }
-
- @Test
- fun isWifiDefault_wifiNetworkLost_isFalse() =
- testScope.runTest {
- val latest by collectLastValue(underTest.isWifiDefault)
-
- // First, add a network
- getDefaultNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
- assertThat(latest).isTrue()
-
- // WHEN the network is lost
- getDefaultNetworkCallback().onLost(NETWORK)
-
- // THEN we update to false
- assertThat(latest).isFalse()
- }
-
- @Test
- fun wifiNetwork_initiallyGetsDefault() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- assertThat(latest).isEqualTo(WIFI_NETWORK_DEFAULT)
- }
-
- @Test
- fun wifiNetwork_primaryWifiNetworkAdded_flowHasNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(SSID)
- whenever(this.isPrimary).thenReturn(true)
- }
- val network = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) }
-
- getNetworkCallback()
- .onCapabilitiesChanged(network, createWifiNetworkCapabilities(wifiInfo))
-
- assertThat(latest is WifiNetworkModel.Active).isTrue()
- val latestActive = latest as WifiNetworkModel.Active
- assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
- assertThat(latestActive.ssid).isEqualTo(SSID)
- }
-
- @Test
- fun wifiNetwork_neverHasHotspot() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(SSID)
- whenever(this.isPrimary).thenReturn(true)
- }
- val network = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) }
-
- getNetworkCallback()
- .onCapabilitiesChanged(network, createWifiNetworkCapabilities(wifiInfo))
-
- assertThat(latest is WifiNetworkModel.Active).isTrue()
- assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
- .isEqualTo(WifiNetworkModel.HotspotDeviceType.NONE)
- }
-
- @Test
- fun wifiNetwork_isCarrierMerged_flowHasCarrierMerged() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.isCarrierMerged).thenReturn(true)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
-
- assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
- }
-
- @Test
- fun wifiNetwork_isCarrierMergedViaUnderlyingWifi_flowHasCarrierMerged() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val underlyingNetwork = mock<Network>()
- val carrierMergedInfo =
- mock<WifiInfo>().apply {
- whenever(this.isCarrierMerged).thenReturn(true)
- whenever(this.isPrimary).thenReturn(true)
- }
- val underlyingWifiCapabilities =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(carrierMergedInfo)
- }
- whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
- .thenReturn(underlyingWifiCapabilities)
-
- // WHEN the main capabilities have an underlying carrier merged network via WIFI
- // transport and WifiInfo
- val mainCapabilities =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(null)
- whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
- }
-
- getNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
-
- // THEN the wifi network is carrier merged
- assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
- }
-
- @Test
- fun wifiNetwork_isCarrierMergedViaUnderlyingCellular_flowHasCarrierMerged() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val underlyingCarrierMergedNetwork = mock<Network>()
- val carrierMergedInfo =
- mock<WifiInfo>().apply {
- whenever(this.isCarrierMerged).thenReturn(true)
- whenever(this.isPrimary).thenReturn(true)
- }
- val underlyingCapabilities =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
- }
- whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
- .thenReturn(underlyingCapabilities)
-
- // WHEN the main capabilities have an underlying carrier merged network via CELLULAR
- // transport and VcnTransportInfo
- val mainCapabilities =
- mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(null)
- whenever(it.underlyingNetworks)
- .thenReturn(listOf(underlyingCarrierMergedNetwork))
- }
-
- getNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
-
- // THEN the wifi network is carrier merged
- assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
- }
-
- @Test
- fun wifiNetwork_carrierMergedButInvalidSubId_flowHasInvalid() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.isCarrierMerged).thenReturn(true)
- whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(
- NETWORK,
- createWifiNetworkCapabilities(wifiInfo),
- )
-
- assertThat(latest).isInstanceOf(WifiNetworkModel.Invalid::class.java)
- }
-
- @Test
- fun wifiNetwork_isCarrierMerged_getsCorrectValues() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val rssi = -57
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.isCarrierMerged).thenReturn(true)
- whenever(this.rssi).thenReturn(rssi)
- whenever(this.subscriptionId).thenReturn(567)
- }
-
- whenever(wifiManager.calculateSignalLevel(rssi)).thenReturn(2)
- whenever(wifiManager.maxSignalLevel).thenReturn(5)
-
- getNetworkCallback()
- .onCapabilitiesChanged(
- NETWORK,
- createWifiNetworkCapabilities(wifiInfo),
- )
-
- assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
- val latestCarrierMerged = latest as WifiNetworkModel.CarrierMerged
- assertThat(latestCarrierMerged.networkId).isEqualTo(NETWORK_ID)
- assertThat(latestCarrierMerged.subscriptionId).isEqualTo(567)
- assertThat(latestCarrierMerged.level).isEqualTo(2)
- // numberOfLevels = maxSignalLevel + 1
- assertThat(latestCarrierMerged.numberOfLevels).isEqualTo(6)
- }
-
- @Test
- fun wifiNetwork_notValidated_networkNotValidated() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- getNetworkCallback()
- .onCapabilitiesChanged(
- NETWORK,
- createWifiNetworkCapabilities(PRIMARY_WIFI_INFO, isValidated = false)
- )
-
- assertThat((latest as WifiNetworkModel.Active).isValidated).isFalse()
- }
-
- @Test
- fun wifiNetwork_validated_networkValidated() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- getNetworkCallback()
- .onCapabilitiesChanged(
- NETWORK,
- createWifiNetworkCapabilities(PRIMARY_WIFI_INFO, isValidated = true)
- )
-
- assertThat((latest as WifiNetworkModel.Active).isValidated).isTrue()
- }
-
- @Test
- fun wifiNetwork_nonPrimaryWifiNetworkAdded_flowHasNoNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(SSID)
- whenever(this.isPrimary).thenReturn(false)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
-
- assertThat(latest is WifiNetworkModel.Inactive).isTrue()
- }
-
- /** Regression test for b/266628069. */
- @Test
- fun wifiNetwork_transportInfoIsNotWifi_flowHasNoNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val transportInfo =
- VpnTransportInfo(
- /* type= */ 0,
- /* sessionId= */ "sessionId",
- )
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(transportInfo))
-
- assertThat(latest is WifiNetworkModel.Inactive).isTrue()
- }
-
- @Test
- fun wifiNetwork_cellularVcnNetworkAdded_flowHasNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val capabilities =
- mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
- }
-
- getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest is WifiNetworkModel.Active).isTrue()
- val latestActive = latest as WifiNetworkModel.Active
- assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
- assertThat(latestActive.ssid).isEqualTo(SSID)
- }
-
- @Test
- fun wifiNetwork_nonPrimaryCellularVcnNetworkAdded_flowHasNoNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(SSID)
- whenever(this.isPrimary).thenReturn(false)
- }
- val capabilities =
- mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(this.transportInfo).thenReturn(VcnTransportInfo(wifiInfo))
- }
-
- getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest is WifiNetworkModel.Inactive).isTrue()
- }
-
- @Test
- fun wifiNetwork_cellularNotVcnNetworkAdded_flowHasNoNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val capabilities =
- mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(this.transportInfo).thenReturn(mock())
- }
-
- getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest is WifiNetworkModel.Inactive).isTrue()
- }
-
- @Test
- fun wifiNetwork_cellularAndWifiTransports_usesCellular() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val capabilities =
- mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
- }
-
- getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest is WifiNetworkModel.Active).isTrue()
- val latestActive = latest as WifiNetworkModel.Active
- assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
- assertThat(latestActive.ssid).isEqualTo(SSID)
- }
-
- @Test
- fun wifiNetwork_newPrimaryWifiNetwork_flowHasNewNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- // Start with the original network
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
-
- // WHEN we update to a new primary network
- val newNetworkId = 456
- val newNetwork =
- mock<Network>().apply { whenever(this.getNetId()).thenReturn(newNetworkId) }
- val newSsid = "CD"
- val newWifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(newSsid)
- whenever(this.isPrimary).thenReturn(true)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(newNetwork, createWifiNetworkCapabilities(newWifiInfo))
-
- // THEN we use the new network
- assertThat(latest is WifiNetworkModel.Active).isTrue()
- val latestActive = latest as WifiNetworkModel.Active
- assertThat(latestActive.networkId).isEqualTo(newNetworkId)
- assertThat(latestActive.ssid).isEqualTo(newSsid)
- }
-
- @Test
- fun wifiNetwork_newNonPrimaryWifiNetwork_flowHasOldNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- // Start with the original network
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
-
- // WHEN we notify of a new but non-primary network
- val newNetworkId = 456
- val newNetwork =
- mock<Network>().apply { whenever(this.getNetId()).thenReturn(newNetworkId) }
- val newSsid = "EF"
- val newWifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(newSsid)
- whenever(this.isPrimary).thenReturn(false)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(newNetwork, createWifiNetworkCapabilities(newWifiInfo))
-
- // THEN we still use the original network
- assertThat(latest is WifiNetworkModel.Active).isTrue()
- val latestActive = latest as WifiNetworkModel.Active
- assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
- assertThat(latestActive.ssid).isEqualTo(SSID)
- }
-
- @Test
- fun wifiNetwork_newNetworkCapabilities_flowHasNewData() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(SSID)
- whenever(this.isPrimary).thenReturn(true)
- }
-
- // Start with the original network
- getNetworkCallback()
- .onCapabilitiesChanged(
- NETWORK,
- createWifiNetworkCapabilities(wifiInfo, isValidated = true)
- )
-
- // WHEN we keep the same network ID but change the SSID
- val newSsid = "CD"
- val newWifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(newSsid)
- whenever(this.isPrimary).thenReturn(true)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(
- NETWORK,
- createWifiNetworkCapabilities(newWifiInfo, isValidated = false)
- )
-
- // THEN we've updated to the new SSID
- assertThat(latest is WifiNetworkModel.Active).isTrue()
- val latestActive = latest as WifiNetworkModel.Active
- assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
- assertThat(latestActive.ssid).isEqualTo(newSsid)
- assertThat(latestActive.isValidated).isFalse()
- }
-
- @Test
- fun wifiNetwork_noCurrentNetwork_networkLost_flowHasNoNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- // WHEN we receive #onLost without any #onCapabilitiesChanged beforehand
- getNetworkCallback().onLost(NETWORK)
-
- // THEN there's no crash and we still have no network
- assertThat(latest is WifiNetworkModel.Inactive).isTrue()
- }
-
- @Test
- fun wifiNetwork_currentActiveNetworkLost_flowHasNoNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
- assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID)
-
- // WHEN we lose our current network
- getNetworkCallback().onLost(NETWORK)
-
- // THEN we update to no network
- assertThat(latest is WifiNetworkModel.Inactive).isTrue()
- }
-
- /** Possible regression test for b/278618530. */
- @Test
- fun wifiNetwork_currentCarrierMergedNetworkLost_flowHasNoNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.isCarrierMerged).thenReturn(true)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
- assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
- assertThat((latest as WifiNetworkModel.CarrierMerged).networkId).isEqualTo(NETWORK_ID)
-
- // WHEN we lose our current network
- getNetworkCallback().onLost(NETWORK)
-
- // THEN we update to no network
- assertThat(latest is WifiNetworkModel.Inactive).isTrue()
- }
-
- @Test
- fun wifiNetwork_unknownNetworkLost_flowHasPreviousNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
- assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID)
-
- // WHEN we lose an unknown network
- val unknownNetwork = mock<Network>().apply { whenever(this.getNetId()).thenReturn(543) }
- getNetworkCallback().onLost(unknownNetwork)
-
- // THEN we still have our previous network
- assertThat(latest is WifiNetworkModel.Active).isTrue()
- val latestActive = latest as WifiNetworkModel.Active
- assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
- assertThat(latestActive.ssid).isEqualTo(SSID)
- }
-
- @Test
- fun wifiNetwork_notCurrentNetworkLost_flowHasCurrentNetwork() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiNetwork)
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
- assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID)
-
- // WHEN we update to a new network...
- val newNetworkId = 89
- val newNetwork =
- mock<Network>().apply { whenever(this.getNetId()).thenReturn(newNetworkId) }
- getNetworkCallback()
- .onCapabilitiesChanged(newNetwork, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
- // ...and lose the old network
- getNetworkCallback().onLost(NETWORK)
-
- // THEN we still have the new network
- assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(newNetworkId)
- }
-
- /** Regression test for b/244173280. */
- @Test
- fun wifiNetwork_multipleSubscribers_newSubscribersGetCurrentValue() =
- testScope.runTest {
- val latest1 by collectLastValue(underTest.wifiNetwork)
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
-
- assertThat(latest1 is WifiNetworkModel.Active).isTrue()
- val latest1Active = latest1 as WifiNetworkModel.Active
- assertThat(latest1Active.networkId).isEqualTo(NETWORK_ID)
- assertThat(latest1Active.ssid).isEqualTo(SSID)
-
- // WHEN we add a second subscriber after having already emitted a value
- val latest2 by collectLastValue(underTest.wifiNetwork)
-
- // THEN the second subscribe receives the already-emitted value
- assertThat(latest2 is WifiNetworkModel.Active).isTrue()
- val latest2Active = latest2 as WifiNetworkModel.Active
- assertThat(latest2Active.networkId).isEqualTo(NETWORK_ID)
- assertThat(latest2Active.ssid).isEqualTo(SSID)
- }
-
- @Test
- fun secondaryNetworks_alwaysEmpty() =
- testScope.runTest {
- val latest by collectLastValue(underTest.secondaryNetworks)
- collectLastValue(underTest.wifiNetwork)
-
- // Even WHEN we do have non-primary wifi info
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(SSID)
- whenever(this.isPrimary).thenReturn(false)
- }
- val network = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) }
-
- getNetworkCallback()
- .onCapabilitiesChanged(network, createWifiNetworkCapabilities(wifiInfo))
-
- // THEN the secondary networks list is empty because this repo doesn't support it
- assertThat(latest).isEmpty()
- }
-
- @Test
- fun isWifiConnectedWithValidSsid_inactiveNetwork_false() =
- testScope.runTest {
- collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(SSID)
- // A non-primary network is inactive
- whenever(this.isPrimary).thenReturn(false)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
-
- assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
- }
-
- @Test
- fun isWifiConnectedWithValidSsid_carrierMergedNetwork_false() =
- testScope.runTest {
- collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.isCarrierMerged).thenReturn(true)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
- testScope.runCurrent()
-
- assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
- }
-
- @Test
- fun isWifiConnectedWithValidSsid_invalidNetwork_false() =
- testScope.runTest {
- collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.isCarrierMerged).thenReturn(true)
- whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(
- NETWORK,
- createWifiNetworkCapabilities(wifiInfo),
- )
- testScope.runCurrent()
-
- assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
- }
-
- @Test
- fun isWifiConnectedWithValidSsid_activeNetwork_nullSsid_false() =
- testScope.runTest {
- collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.ssid).thenReturn(null)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
- testScope.runCurrent()
-
- assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
- }
-
- @Test
- fun isWifiConnectedWithValidSsid_activeNetwork_unknownSsid_false() =
- testScope.runTest {
- collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.ssid).thenReturn(UNKNOWN_SSID)
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
- testScope.runCurrent()
-
- assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
- }
-
- @Test
- fun isWifiConnectedWithValidSsid_activeNetwork_validSsid_true() =
- testScope.runTest {
- collectLastValue(underTest.wifiNetwork)
-
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.ssid).thenReturn("FakeSsid")
- }
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
- testScope.runCurrent()
-
- assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue()
- }
-
- @Test
- fun isWifiConnectedWithValidSsid_activeToInactive_trueToFalse() =
- testScope.runTest {
- collectLastValue(underTest.wifiNetwork)
-
- // Start with active
- val wifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.ssid).thenReturn("FakeSsid")
- }
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
- testScope.runCurrent()
-
- assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue()
-
- // WHEN the network is lost
- getNetworkCallback().onLost(NETWORK)
- testScope.runCurrent()
-
- // THEN the isWifiConnected updates
- assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
- }
-
- @Test
- fun wifiActivity_callbackGivesNone_activityFlowHasNone() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiActivity)
-
- getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_NONE)
-
- assertThat(latest)
- .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
- }
-
- @Test
- fun wifiActivity_callbackGivesIn_activityFlowHasIn() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiActivity)
-
- getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_IN)
-
- assertThat(latest)
- .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = false))
- }
-
- @Test
- fun wifiActivity_callbackGivesOut_activityFlowHasOut() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiActivity)
-
- getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_OUT)
-
- assertThat(latest)
- .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = true))
- }
-
- @Test
- fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiActivity)
-
- getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_INOUT)
-
- assertThat(latest)
- .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = true))
- }
-
- @Test
- fun wifiScanResults_containsSsidList() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiScanResults)
-
- val scanResults =
- listOf(
- ScanResult().also { it.SSID = "ssid 1" },
- ScanResult().also { it.SSID = "ssid 2" },
- ScanResult().also { it.SSID = "ssid 3" },
- ScanResult().also { it.SSID = "ssid 4" },
- ScanResult().also { it.SSID = "ssid 5" },
- )
- whenever(wifiManager.scanResults).thenReturn(scanResults)
- getScanResultsCallback().onScanResultsAvailable()
-
- val expected =
- listOf(
- WifiScanEntry(ssid = "ssid 1"),
- WifiScanEntry(ssid = "ssid 2"),
- WifiScanEntry(ssid = "ssid 3"),
- WifiScanEntry(ssid = "ssid 4"),
- WifiScanEntry(ssid = "ssid 5"),
- )
-
- assertThat(latest).isEqualTo(expected)
- }
-
- @Test
- fun wifiScanResults_updates() =
- testScope.runTest {
- val latest by collectLastValue(underTest.wifiScanResults)
-
- var scanResults =
- listOf(
- ScanResult().also { it.SSID = "ssid 1" },
- ScanResult().also { it.SSID = "ssid 2" },
- ScanResult().also { it.SSID = "ssid 3" },
- ScanResult().also { it.SSID = "ssid 4" },
- ScanResult().also { it.SSID = "ssid 5" },
- )
- whenever(wifiManager.scanResults).thenReturn(scanResults)
- getScanResultsCallback().onScanResultsAvailable()
-
- // New scan representing no results
- scanResults = emptyList()
- whenever(wifiManager.scanResults).thenReturn(scanResults)
- getScanResultsCallback().onScanResultsAvailable()
-
- assertThat(latest).isEmpty()
- }
-
- private fun createRepo(): WifiRepositoryImpl {
- return WifiRepositoryImpl(
- fakeBroadcastDispatcher,
- connectivityManager,
- connectivityRepository,
- logger,
- tableLogger,
- executor,
- dispatcher,
- testScope.backgroundScope,
- wifiManager,
- )
- }
-
- private fun getTrafficStateCallback(): TrafficStateCallback {
- testScope.runCurrent()
- val callbackCaptor = argumentCaptor<TrafficStateCallback>()
- verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture())
- return callbackCaptor.value!!
- }
-
- private fun getNetworkCallback(): ConnectivityManager.NetworkCallback {
- testScope.runCurrent()
- val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
- verify(connectivityManager).registerNetworkCallback(any(), callbackCaptor.capture())
- return callbackCaptor.value!!
- }
-
- private fun getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback {
- testScope.runCurrent()
- val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
- verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture())
- return callbackCaptor.value!!
- }
-
- private fun getScanResultsCallback(): WifiManager.ScanResultsCallback {
- testScope.runCurrent()
- val callbackCaptor = argumentCaptor<WifiManager.ScanResultsCallback>()
- verify(wifiManager).registerScanResultsCallback(any(), callbackCaptor.capture())
- return callbackCaptor.value!!
- }
-
- private fun createWifiNetworkCapabilities(
- transportInfo: TransportInfo,
- isValidated: Boolean = true,
- ): NetworkCapabilities {
- return mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(transportInfo)
- whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(isValidated)
- }
- }
-
- private companion object {
- const val NETWORK_ID = 45
- val NETWORK = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) }
- const val SSID = "AB"
- val PRIMARY_WIFI_INFO: WifiInfo =
- mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(SSID)
- whenever(this.isPrimary).thenReturn(true)
- }
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt
new file mode 100644
index 000000000000..55e46dc1c434
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2020 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.wmshell
+
+import android.content.pm.UserInfo
+import android.graphics.Color
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.keyguardUpdateMonitor
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.ui.viewmodel.communalTransitionViewModel
+import com.android.systemui.communal.util.fakeCommunalColors
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.dock.DockManager
+import com.android.systemui.dock.fakeDockManager
+import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.ScreenLifecycle
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.wakefulnessLifecycle
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.model.SysUiState
+import com.android.systemui.model.sysUiState
+import com.android.systemui.notetask.NoteTaskInitializer
+import com.android.systemui.settings.FakeDisplayTracker
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.settings.userTracker
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.commandQueue
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.configurationController
+import com.android.systemui.statusbar.policy.keyguardStateController
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.wm.shell.desktopmode.DesktopMode
+import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
+import com.android.wm.shell.onehanded.OneHanded
+import com.android.wm.shell.onehanded.OneHandedEventCallback
+import com.android.wm.shell.onehanded.OneHandedTransitionCallback
+import com.android.wm.shell.pip.Pip
+import com.android.wm.shell.recents.RecentTasks
+import com.android.wm.shell.splitscreen.SplitScreen
+import com.android.wm.shell.sysui.ShellInterface
+import java.util.Optional
+import java.util.concurrent.Executor
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for [WMShell].
+ *
+ * Build/Install/Run: atest SystemUITests:WMShellTest
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WMShellTest : SysuiTestCase() {
+ val kosmos = testKosmos()
+ val testScope = kosmos.testScope
+
+ @Mock private lateinit var mShellInterface: ShellInterface
+ @Mock private lateinit var mScreenLifecycle: ScreenLifecycle
+ @Mock private lateinit var mPip: Pip
+ @Mock private lateinit var mSplitScreen: SplitScreen
+ @Mock private lateinit var mOneHanded: OneHanded
+ @Mock private lateinit var mNoteTaskInitializer: NoteTaskInitializer
+ @Mock private lateinit var mDesktopMode: DesktopMode
+ @Mock private lateinit var mRecentTasks: RecentTasks
+
+ private val mCommandQueue: CommandQueue = kosmos.commandQueue
+ private val mConfigurationController: ConfigurationController = kosmos.configurationController
+ private val mKeyguardStateController: KeyguardStateController = kosmos.keyguardStateController
+ private val mKeyguardUpdateMonitor: KeyguardUpdateMonitor = kosmos.keyguardUpdateMonitor
+ private val mSysUiState: SysUiState = kosmos.sysUiState
+ private val mWakefulnessLifecycle: WakefulnessLifecycle = kosmos.wakefulnessLifecycle
+ private val mUserTracker: UserTracker = kosmos.userTracker
+ private val mSysUiMainExecutor: Executor = kosmos.fakeExecutor
+ private val communalTransitionViewModel = kosmos.communalTransitionViewModel
+
+ private lateinit var underTest: WMShell
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ val displayTracker = FakeDisplayTracker(mContext)
+
+ kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO))
+ kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
+
+ underTest =
+ WMShell(
+ mContext,
+ mShellInterface,
+ Optional.of(mPip),
+ Optional.of(mSplitScreen),
+ Optional.of(mOneHanded),
+ Optional.of(mDesktopMode),
+ Optional.of(mRecentTasks),
+ mCommandQueue,
+ mConfigurationController,
+ mKeyguardStateController,
+ mKeyguardUpdateMonitor,
+ mScreenLifecycle,
+ mSysUiState,
+ mWakefulnessLifecycle,
+ mUserTracker,
+ displayTracker,
+ mNoteTaskInitializer,
+ communalTransitionViewModel,
+ JavaAdapter(testScope.backgroundScope),
+ mSysUiMainExecutor
+ )
+ }
+
+ @Test
+ fun initPip_registersCommandQueueCallback() {
+ underTest.initPip(mPip)
+ verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks::class.java))
+ }
+
+ @Test
+ fun initOneHanded_registersCallbacks() {
+ underTest.initOneHanded(mOneHanded)
+ verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks::class.java))
+ verify(mScreenLifecycle).addObserver(any(ScreenLifecycle.Observer::class.java))
+ verify(mOneHanded).registerTransitionCallback(any(OneHandedTransitionCallback::class.java))
+ verify(mOneHanded).registerEventCallback(any(OneHandedEventCallback::class.java))
+ }
+
+ @Test
+ fun initDesktopMode_registersListener() {
+ underTest.initDesktopMode(mDesktopMode)
+ verify(mDesktopMode)
+ .addVisibleTasksListener(
+ any(VisibleTasksListener::class.java),
+ any(Executor::class.java)
+ )
+ }
+
+ @Test
+ fun initRecentTasks_registersListener() {
+ underTest.initRecentTasks(mRecentTasks)
+ verify(mRecentTasks).addAnimationStateListener(any(Executor::class.java), any())
+ }
+
+ @Test
+ @EnableFlags(FLAG_COMMUNAL_HUB)
+ fun initRecentTasks_setRecentsBackgroundColorWhenCommunal() =
+ testScope.runTest {
+ val black = Color.valueOf(Color.BLACK)
+ kosmos.fakeCommunalColors.setBackgroundColor(black)
+
+ kosmos.fakeKeyguardRepository.setKeyguardShowing(false)
+
+ underTest.initRecentTasks(mRecentTasks)
+ runCurrent()
+ verify(mRecentTasks).setTransitionBackgroundColor(null)
+ verify(mRecentTasks, never()).setTransitionBackgroundColor(black)
+
+ setDocked(true)
+ // Make communal available
+ kosmos.fakeKeyguardRepository.setIsEncryptedOrLockdown(false)
+ kosmos.fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
+ kosmos.fakeKeyguardRepository.setKeyguardShowing(true)
+
+ runCurrent()
+
+ verify(mRecentTasks).setTransitionBackgroundColor(black)
+ }
+
+ private fun TestScope.setDocked(docked: Boolean) {
+ kosmos.fakeDockManager.setIsDocked(docked)
+ val event =
+ if (docked) {
+ DockManager.STATE_DOCKED
+ } else {
+ DockManager.STATE_NONE
+ }
+ kosmos.fakeDockManager.setDockEvent(event)
+ runCurrent()
+ }
+
+ private companion object {
+ val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+ }
+}
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/styles.xml b/packages/SystemUI/res/color/notification_focus_overlay_color.xml
index fff41c3583f2..6a3c7a148963 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/styles.xml
+++ b/packages/SystemUI/res/color/notification_focus_overlay_color.xml
@@ -14,11 +14,9 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<resources>
- <style name="TextAppearance.TopIntroText"
- parent="@android:style/TextAppearance.DeviceDefault">
- <item name="android:textSize">14sp</item>
- <item name="android:textColor">@color/settingslib_materialColorOnSurfaceVariant</item>
- </style>
-</resources> \ No newline at end of file
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:state_focused="true" android:color="?androidprv:attr/materialColorSecondary" />
+ <item android:color="@color/transparent" />
+</selector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/notification_material_bg.xml b/packages/SystemUI/res/drawable/notification_material_bg.xml
index 3f903aece0b5..587a5a05458f 100644
--- a/packages/SystemUI/res/drawable/notification_material_bg.xml
+++ b/packages/SystemUI/res/drawable/notification_material_bg.xml
@@ -28,4 +28,9 @@
<solid android:color="@color/notification_state_color_default" />
</shape>
</item>
+ <item>
+ <shape>
+ <stroke android:width="3dp" android:color="@color/notification_focus_overlay_color"/>
+ </shape>
+ </item>
</layer-list> \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java
index 5bd85a72b06f..429d3f09fa6a 100644
--- a/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java
@@ -14,6 +14,8 @@
package com.android.systemui;
+import static com.android.systemui.Flags.sliceBroadcastRelayInBackground;
+
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentProvider;
@@ -23,13 +25,19 @@ import android.content.IntentFilter;
import android.net.Uri;
import android.os.UserHandle;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.Log;
+import androidx.annotation.GuardedBy;
+import androidx.annotation.WorkerThread;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.SliceBroadcastRelay;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
+
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -42,14 +50,18 @@ public class SliceBroadcastRelayHandler implements CoreStartable {
private static final String TAG = "SliceBroadcastRelay";
private static final boolean DEBUG = false;
+ @GuardedBy("mRelays")
private final ArrayMap<Uri, BroadcastRelay> mRelays = new ArrayMap<>();
private final Context mContext;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final Executor mBackgroundExecutor;
@Inject
- public SliceBroadcastRelayHandler(Context context, BroadcastDispatcher broadcastDispatcher) {
+ public SliceBroadcastRelayHandler(Context context, BroadcastDispatcher broadcastDispatcher,
+ @Background Executor backgroundExecutor) {
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
+ mBackgroundExecutor = backgroundExecutor;
}
@Override
@@ -57,21 +69,29 @@ public class SliceBroadcastRelayHandler implements CoreStartable {
if (DEBUG) Log.d(TAG, "Start");
IntentFilter filter = new IntentFilter(SliceBroadcastRelay.ACTION_REGISTER);
filter.addAction(SliceBroadcastRelay.ACTION_UNREGISTER);
- mBroadcastDispatcher.registerReceiver(mReceiver, filter);
+
+ if (sliceBroadcastRelayInBackground()) {
+ mBroadcastDispatcher.registerReceiver(mReceiver, filter, mBackgroundExecutor);
+ } else {
+ mBroadcastDispatcher.registerReceiver(mReceiver, filter);
+ }
}
// This does not use BroadcastDispatcher as the filter may have schemas or mime types.
+ @WorkerThread
@VisibleForTesting
void handleIntent(Intent intent) {
if (SliceBroadcastRelay.ACTION_REGISTER.equals(intent.getAction())) {
- Uri uri = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_URI);
+ Uri uri = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_URI, Uri.class);
ComponentName receiverClass =
- intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_RECEIVER);
- IntentFilter filter = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_FILTER);
+ intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_RECEIVER,
+ ComponentName.class);
+ IntentFilter filter = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_FILTER,
+ IntentFilter.class);
if (DEBUG) Log.d(TAG, "Register " + uri + " " + receiverClass + " " + filter);
getOrCreateRelay(uri).register(mContext, receiverClass, filter);
} else if (SliceBroadcastRelay.ACTION_UNREGISTER.equals(intent.getAction())) {
- Uri uri = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_URI);
+ Uri uri = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_URI, Uri.class);
if (DEBUG) Log.d(TAG, "Unregister " + uri);
BroadcastRelay relay = getAndRemoveRelay(uri);
if (relay != null) {
@@ -80,17 +100,23 @@ public class SliceBroadcastRelayHandler implements CoreStartable {
}
}
+ @WorkerThread
private BroadcastRelay getOrCreateRelay(Uri uri) {
- BroadcastRelay ret = mRelays.get(uri);
- if (ret == null) {
- ret = new BroadcastRelay(uri);
- mRelays.put(uri, ret);
+ synchronized (mRelays) {
+ BroadcastRelay ret = mRelays.get(uri);
+ if (ret == null) {
+ ret = new BroadcastRelay(uri);
+ mRelays.put(uri, ret);
+ }
+ return ret;
}
- return ret;
}
+ @WorkerThread
private BroadcastRelay getAndRemoveRelay(Uri uri) {
- return mRelays.remove(uri);
+ synchronized (mRelays) {
+ return mRelays.remove(uri);
+ }
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -102,7 +128,7 @@ public class SliceBroadcastRelayHandler implements CoreStartable {
private static class BroadcastRelay extends BroadcastReceiver {
- private final ArraySet<ComponentName> mReceivers = new ArraySet<>();
+ private final CopyOnWriteArraySet<ComponentName> mReceivers = new CopyOnWriteArraySet<>();
private final UserHandle mUserId;
private final Uri mUri;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
index af8149f59a16..0bd6d6ede9ef 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
@@ -18,8 +18,15 @@ package com.android.systemui.accessibility;
import static android.view.WindowManager.LayoutParams;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
import android.annotation.UiContext;
+import android.content.ComponentCallbacks;
import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Region;
@@ -30,56 +37,123 @@ import android.view.SurfaceControlViewHost;
import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Interpolator;
+import androidx.annotation.NonNull;
import androidx.annotation.UiThread;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.res.R;
+import java.util.concurrent.Executor;
import java.util.function.Supplier;
-class FullscreenMagnificationController {
+class FullscreenMagnificationController implements ComponentCallbacks {
private final Context mContext;
private final AccessibilityManager mAccessibilityManager;
private final WindowManager mWindowManager;
private Supplier<SurfaceControlViewHost> mScvhSupplier;
- private SurfaceControlViewHost mSurfaceControlViewHost;
+ private SurfaceControlViewHost mSurfaceControlViewHost = null;
+ private SurfaceControl mBorderSurfaceControl = null;
private Rect mWindowBounds;
private SurfaceControl.Transaction mTransaction;
private View mFullscreenBorder = null;
private int mBorderOffset;
private final int mDisplayId;
private static final Region sEmptyRegion = new Region();
+ private ValueAnimator mShowHideBorderAnimator;
+ private Executor mExecutor;
+ private boolean mFullscreenMagnificationActivated = false;
+ private final Configuration mConfiguration;
FullscreenMagnificationController(
@UiContext Context context,
+ Executor executor,
AccessibilityManager accessibilityManager,
WindowManager windowManager,
Supplier<SurfaceControlViewHost> scvhSupplier) {
+ this(context, executor, accessibilityManager, windowManager, scvhSupplier,
+ new SurfaceControl.Transaction(), createNullTargetObjectAnimator(context));
+ }
+
+ @VisibleForTesting
+ FullscreenMagnificationController(
+ @UiContext Context context,
+ @Main Executor executor,
+ AccessibilityManager accessibilityManager,
+ WindowManager windowManager,
+ Supplier<SurfaceControlViewHost> scvhSupplier,
+ SurfaceControl.Transaction transaction,
+ ValueAnimator valueAnimator) {
mContext = context;
+ mExecutor = executor;
mAccessibilityManager = accessibilityManager;
mWindowManager = windowManager;
mWindowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
- mTransaction = new SurfaceControl.Transaction();
+ mTransaction = transaction;
mScvhSupplier = scvhSupplier;
mBorderOffset = mContext.getResources().getDimensionPixelSize(
R.dimen.magnifier_border_width_fullscreen_with_offset)
- mContext.getResources().getDimensionPixelSize(
R.dimen.magnifier_border_width_fullscreen);
mDisplayId = mContext.getDisplayId();
+ mConfiguration = new Configuration(context.getResources().getConfiguration());
+ mShowHideBorderAnimator = valueAnimator;
+ mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
+ if (isReverse) {
+ // The animation was played in reverse, which means we are hiding the border.
+ // We would like to perform clean up after the border is fully hidden.
+ cleanUpBorder();
+ }
+ }
+ });
}
+ private static ValueAnimator createNullTargetObjectAnimator(Context context) {
+ final ValueAnimator valueAnimator =
+ ObjectAnimator.ofFloat(/* target= */ null, View.ALPHA, 0f, 1f);
+ Interpolator interpolator = new AccelerateDecelerateInterpolator();
+ final long longAnimationDuration = context.getResources().getInteger(
+ com.android.internal.R.integer.config_longAnimTime);
+
+ valueAnimator.setInterpolator(interpolator);
+ valueAnimator.setDuration(longAnimationDuration);
+ return valueAnimator;
+ }
+
+ /**
+ * Check the fullscreen magnification activation status, and proceed corresponding actions when
+ * there is an activation change.
+ */
@UiThread
void onFullscreenMagnificationActivationChanged(boolean activated) {
- if (activated) {
- createFullscreenMagnificationBorder();
- } else {
- removeFullscreenMagnificationBorder();
+ final boolean changed = (mFullscreenMagnificationActivated != activated);
+ if (changed) {
+ mFullscreenMagnificationActivated = activated;
+ if (activated) {
+ createFullscreenMagnificationBorder();
+ } else {
+ removeFullscreenMagnificationBorder();
+ }
}
}
+ /**
+ * This method should only be called when fullscreen magnification is changed from activated
+ * to inactivated.
+ */
@UiThread
private void removeFullscreenMagnificationBorder() {
+ mContext.unregisterComponentCallbacks(this);
+ mShowHideBorderAnimator.reverse();
+ }
+
+ private void cleanUpBorder() {
if (mSurfaceControlViewHost != null) {
mSurfaceControlViewHost.release();
mSurfaceControlViewHost = null;
@@ -91,31 +165,57 @@ class FullscreenMagnificationController {
}
/**
- * Since the device corners are not perfectly rounded, we would like to create a thick stroke,
- * and set negative offset to the border view to fill up the spaces between the border and the
- * device corners.
+ * This method should only be called when fullscreen magnification is changed from inactivated
+ * to activated.
*/
@UiThread
private void createFullscreenMagnificationBorder() {
- mFullscreenBorder = LayoutInflater.from(mContext)
- .inflate(R.layout.fullscreen_magnification_border, null);
- mSurfaceControlViewHost = mScvhSupplier.get();
- mSurfaceControlViewHost.setView(mFullscreenBorder, getBorderLayoutParams());
+ onConfigurationChanged(mContext.getResources().getConfiguration());
+ mContext.registerComponentCallbacks(this);
- SurfaceControl surfaceControl = mSurfaceControlViewHost
- .getSurfacePackage().getSurfaceControl();
+ if (mSurfaceControlViewHost == null) {
+ // Create the view only if it does not exist yet. If we are trying to enable fullscreen
+ // magnification before it was fully disabled, we use the previous view instead of
+ // creating a new one.
+ mFullscreenBorder = LayoutInflater.from(mContext)
+ .inflate(R.layout.fullscreen_magnification_border, null);
+ // Set the initial border view alpha manually so we won't show the border accidentally
+ // after we apply show() to the SurfaceControl and before the animation starts to run.
+ mFullscreenBorder.setAlpha(0f);
+ mShowHideBorderAnimator.setTarget(mFullscreenBorder);
+ mSurfaceControlViewHost = mScvhSupplier.get();
+ mSurfaceControlViewHost.setView(mFullscreenBorder, getBorderLayoutParams());
+ mBorderSurfaceControl = mSurfaceControlViewHost.getSurfacePackage().getSurfaceControl();
+ }
mTransaction
- .setPosition(surfaceControl, -mBorderOffset, -mBorderOffset)
- .setLayer(surfaceControl, Integer.MAX_VALUE)
- .show(surfaceControl)
+ .addTransactionCommittedListener(
+ mExecutor,
+ () -> {
+ if (mShowHideBorderAnimator.isRunning()) {
+ // Since the method is only called when there is an activation
+ // status change, the running animator is hiding the border.
+ mShowHideBorderAnimator.reverse();
+ } else {
+ mShowHideBorderAnimator.start();
+ }
+ })
+ .setPosition(mBorderSurfaceControl, -mBorderOffset, -mBorderOffset)
+ .setLayer(mBorderSurfaceControl, Integer.MAX_VALUE)
+ .show(mBorderSurfaceControl)
.apply();
- mAccessibilityManager.attachAccessibilityOverlayToDisplay(mDisplayId, surfaceControl);
+ mAccessibilityManager.attachAccessibilityOverlayToDisplay(
+ mDisplayId, mBorderSurfaceControl);
applyTouchableRegion();
}
+ /**
+ * Since the device corners are not perfectly rounded, we would like to create a thick stroke,
+ * and set negative offset to the border view to fill up the spaces between the border and the
+ * device corners.
+ */
private LayoutParams getBorderLayoutParams() {
LayoutParams params = new LayoutParams(
mWindowBounds.width() + 2 * mBorderOffset,
@@ -137,4 +237,41 @@ class FullscreenMagnificationController {
// all touch events to go through this view.
surfaceControl.setTouchableRegion(sEmptyRegion);
}
+
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ final int configDiff = newConfig.diff(mConfiguration);
+ mConfiguration.setTo(newConfig);
+ onConfigurationChanged(configDiff);
+ }
+
+ @VisibleForTesting
+ void onConfigurationChanged(int configDiff) {
+ boolean reCreateWindow = false;
+ if ((configDiff & ActivityInfo.CONFIG_DENSITY) != 0
+ || (configDiff & ActivityInfo.CONFIG_SCREEN_SIZE) != 0
+ || (configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) {
+ updateDimensions();
+ mWindowBounds.set(mWindowManager.getCurrentWindowMetrics().getBounds());
+ reCreateWindow = true;
+ }
+
+ if (mFullscreenBorder != null && reCreateWindow) {
+ final int newWidth = mWindowBounds.width() + 2 * mBorderOffset;
+ final int newHeight = mWindowBounds.height() + 2 * mBorderOffset;
+ mSurfaceControlViewHost.relayout(newWidth, newHeight);
+ }
+ }
+
+ private void updateDimensions() {
+ mBorderOffset = mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnifier_border_width_fullscreen_with_offset)
+ - mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnifier_border_width_fullscreen);
+ }
+
+ @Override
+ public void onLowMemory() {
+
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
index 70165f377a7c..177d933f2d9f 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
@@ -54,6 +54,7 @@ import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.util.settings.SecureSettings;
import java.io.PrintWriter;
+import java.util.concurrent.Executor;
import java.util.function.Supplier;
import javax.inject.Inject;
@@ -71,6 +72,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
private final ModeSwitchesController mModeSwitchesController;
private final Context mContext;
private final Handler mHandler;
+ private final Executor mExecutor;
private final AccessibilityManager mAccessibilityManager;
private final CommandQueue mCommandQueue;
private final OverviewProxyService mOverviewProxyService;
@@ -139,12 +141,13 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
DisplayIdIndexSupplier<FullscreenMagnificationController> {
private final Context mContext;
+ private final Executor mExecutor;
- FullscreenMagnificationControllerSupplier(Context context, Handler handler,
- DisplayManager displayManager, SysUiState sysUiState,
- SecureSettings secureSettings) {
+ FullscreenMagnificationControllerSupplier(Context context, DisplayManager displayManager,
+ Executor executor) {
super(displayManager);
mContext = context;
+ mExecutor = executor;
}
@Override
@@ -156,6 +159,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
windowContext.setTheme(com.android.systemui.res.R.style.Theme_SystemUI);
return new FullscreenMagnificationController(
windowContext,
+ mExecutor,
windowContext.getSystemService(AccessibilityManager.class),
windowContext.getSystemService(WindowManager.class),
scvhSupplier);
@@ -200,13 +204,14 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
DisplayIdIndexSupplier<MagnificationSettingsController> mMagnificationSettingsSupplier;
@Inject
- public Magnification(Context context, @Main Handler mainHandler,
+ public Magnification(Context context, @Main Handler mainHandler, @Main Executor executor,
CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
SysUiState sysUiState, OverviewProxyService overviewProxyService,
SecureSettings secureSettings, DisplayTracker displayTracker,
DisplayManager displayManager, AccessibilityLogger a11yLogger) {
mContext = context;
mHandler = mainHandler;
+ mExecutor = executor;
mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
mCommandQueue = commandQueue;
mModeSwitchesController = modeSwitchesController;
@@ -218,7 +223,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
mHandler, mWindowMagnifierCallback,
displayManager, sysUiState, secureSettings);
mFullscreenMagnificationControllerSupplier = new FullscreenMagnificationControllerSupplier(
- context, mHandler, displayManager, sysUiState, secureSettings);
+ context, displayManager, mExecutor);
mMagnificationSettingsSupplier = new SettingsSupplier(context,
mMagnificationSettingsControllerCallback, displayManager, secureSettings);
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
index 179fa874db79..eaca2767a2e8 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
@@ -1,6 +1,9 @@
package com.android.systemui.bouncer.ui.binder
import android.view.ViewGroup
+import androidx.activity.OnBackPressedDispatcher
+import androidx.activity.OnBackPressedDispatcherOwner
+import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
import androidx.compose.ui.platform.ComposeView
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
@@ -30,7 +33,24 @@ object ComposeBouncerViewBinder {
) {
view.addView(
ComposeView(view.context).apply {
- setContent { PlatformTheme { BouncerContent(viewModel, dialogFactory) } }
+ repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ setViewTreeOnBackPressedDispatcherOwner(
+ object : OnBackPressedDispatcherOwner {
+ override val onBackPressedDispatcher =
+ OnBackPressedDispatcher().apply {
+ setOnBackInvokedDispatcher(
+ view.viewRootImpl.onBackInvokedDispatcher
+ )
+ }
+
+ override val lifecycle: Lifecycle =
+ this@repeatWhenAttached.lifecycle
+ }
+ )
+ setContent { PlatformTheme { BouncerContent(viewModel, dialogFactory) } }
+ }
+ }
}
)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index 72dcb26b089a..27af99ef8014 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -24,6 +24,8 @@ import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryM
import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule
import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule
import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.communal.util.CommunalColors
+import com.android.systemui.communal.util.CommunalColorsImpl
import com.android.systemui.communal.widgets.CommunalWidgetModule
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.communal.widgets.EditWidgetsActivityStarterImpl
@@ -60,6 +62,8 @@ interface CommunalModule {
@Communal
fun bindCommunalSceneDataSource(@Communal delegator: SceneDataSourceDelegator): SceneDataSource
+ @Binds fun bindCommunalColors(impl: CommunalColorsImpl): CommunalColors
+
companion object {
@Provides
@Communal
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index b3002cd6cbdc..3f92223fb57b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -63,7 +63,7 @@ constructor(
override val isEditMode = true
- // Only widgets are editable. The CTA tile comes last in the list and remains visible.
+ // Only widgets are editable.
override val communalContent: Flow<List<CommunalContentModel>> =
communalInteractor.widgetContent.onEach { models ->
logger.d({ "Content updated: $str1" }) { str1 = models.joinToString { it.key } }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
index bdf4e721a551..1bee83b41dbe 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
@@ -16,7 +16,9 @@
package com.android.systemui.communal.ui.viewmodel
+import android.graphics.Color
import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.util.CommunalColors
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -28,6 +30,7 @@ import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGlanceableHubTrans
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.merge
@@ -38,6 +41,7 @@ import kotlinx.coroutines.flow.merge
class CommunalTransitionViewModel
@Inject
constructor(
+ communalColors: CommunalColors,
glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel,
lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel,
dreamToGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel,
@@ -68,4 +72,13 @@ constructor(
step.transitionState == TransitionState.FINISHED ||
step.transitionState == TransitionState.CANCELED
}
+
+ val recentsBackgroundColor: Flow<Color?> =
+ combine(showByDefault, communalColors.backgroundColor) { showByDefault, backgroundColor ->
+ if (showByDefault) {
+ backgroundColor
+ } else {
+ null
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/CommunalColors.kt b/packages/SystemUI/src/com/android/systemui/communal/util/CommunalColors.kt
new file mode 100644
index 000000000000..1e04fe7b6eb0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/util/CommunalColors.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.communal.util
+
+import android.content.Context
+import android.graphics.Color
+import com.android.settingslib.Utils
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** Wrapper around colors used for the communal UI. */
+interface CommunalColors {
+ /** The background color of the glanceable hub. */
+ val backgroundColor: StateFlow<Color>
+}
+
+@SysUISingleton
+class CommunalColorsImpl
+@Inject
+constructor(
+ @Application applicationScope: CoroutineScope,
+ private val context: Context,
+ configurationInteractor: ConfigurationInteractor,
+) : CommunalColors {
+ override val backgroundColor: StateFlow<Color> =
+ configurationInteractor.onAnyConfigurationChange
+ .map { loadBackgroundColor() }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = loadBackgroundColor()
+ )
+
+ private fun loadBackgroundColor(): Color =
+ Color.valueOf(
+ Utils.getColorAttrDefaultColor(
+ context,
+ com.android.internal.R.attr.materialColorOutlineVariant
+ )
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 21ee5bd92328..23fc8ace0223 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -55,6 +55,7 @@ import com.android.systemui.statusbar.gesture.GesturePointerEventListener
import com.android.systemui.statusbar.notification.InstantAppNotifier
import com.android.systemui.statusbar.phone.ScrimController
import com.android.systemui.statusbar.phone.StatusBarHeadsUpChangeListener
+import com.android.systemui.statusbar.policy.BatteryControllerStartable
import com.android.systemui.stylus.StylusUsiPowerStartable
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.theme.ThemeOverlayController
@@ -343,4 +344,10 @@ abstract class SystemUICoreStartableModule {
@IntoMap
@ClassKey(HomeControlsDreamStartable::class)
abstract fun bindHomeControlsDreamStartable(impl: HomeControlsDreamStartable): CoreStartable
+
+ /** Binds {@link BatteryControllerStartable} as a {@link CoreStartable}. */
+ @Binds
+ @IntoMap
+ @ClassKey(BatteryControllerStartable::class)
+ abstract fun bindsBatteryControllerStartable(impl: BatteryControllerStartable): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index db6b8fea9235..04f1ad2cdd37 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -255,9 +255,6 @@ object Flags {
val FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS =
releasedFlag("filter_provisioning_network_subscriptions")
- // TODO(b/292533677): Tracking Bug
- val WIFI_TRACKER_LIB_FOR_WIFI_ICON = releasedFlag("wifi_tracker_lib_for_wifi_icon")
-
// TODO(b/293863612): Tracking Bug
@JvmField val INCOMPATIBLE_CHARGING_BATTERY_ICON =
releasedFlag("incompatible_charging_battery_icon")
@@ -367,16 +364,6 @@ object Flags {
val WM_ALWAYS_ENFORCE_PREDICTIVE_BACK =
sysPropBooleanFlag("persist.wm.debug.predictive_back_always_enforce", default = false)
- // TODO(b/254512728): Tracking Bug
- @JvmField val NEW_BACK_AFFORDANCE = releasedFlag("new_back_affordance")
-
-
- // TODO(b/270987164): Tracking Bug
- @JvmField val TRACKPAD_GESTURE_FEATURES = releasedFlag("trackpad_gesture_features")
-
- // TODO(b/273800936): Tracking Bug
- @JvmField val TRACKPAD_GESTURE_COMMON = releasedFlag("trackpad_gesture_common")
-
// TODO(b/251205791): Tracking Bug
@JvmField val SCREENSHOT_APP_CLIPS = releasedFlag("screenshot_app_clips")
@@ -487,12 +474,6 @@ object Flags {
@JvmField val DECOUPLE_REMOTE_INPUT_DELEGATE_AND_CALLBACK_UPDATE =
unreleasedFlag("decouple_remote_input_delegate_and_callback_update")
- // 2900 - CentralSurfaces-related flags
-
- // TODO(b/285174336): Tracking Bug
- @JvmField
- val USE_REPOS_FOR_BOUNCER_SHOWING = releasedFlag("use_repos_for_bouncer_showing")
-
/** TODO(b/296223317): Enables the new keyguard presentation containing a clock. */
@JvmField
val ENABLE_CLOCK_KEYGUARD_PRESENTATION = releasedFlag("enable_clock_keyguard_presentation")
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
index 4327d18da97e..bccc3c5dc284 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -17,9 +17,7 @@
package com.android.systemui.haptics.qs
import android.animation.ValueAnimator
-import android.annotation.SuppressLint
import android.os.VibrationEffect
-import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import android.view.animation.AccelerateDecelerateInterpolator
@@ -27,18 +25,14 @@ import androidx.annotation.VisibleForTesting
import androidx.core.animation.doOnCancel
import androidx.core.animation.doOnEnd
import androidx.core.animation.doOnStart
-import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.statusbar.VibratorHelper
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
/**
* A class that handles the long press visuo-haptic effect for a QS tile.
@@ -55,34 +49,32 @@ class QSLongPressEffect
@Inject
constructor(
private val vibratorHelper: VibratorHelper?,
- val keyguardInteractor: KeyguardInteractor,
- @Background bgScope: CoroutineScope,
-) : View.OnTouchListener {
+ keyguardInteractor: KeyguardInteractor,
+) {
private var effectDuration = 0
/** Current state */
private var _state = MutableStateFlow(State.IDLE)
- val state = _state.stateIn(bgScope, SharingStarted.Lazily, State.IDLE)
+ val state = _state.asStateFlow()
/** Flows for view control and action */
private val _effectProgress = MutableStateFlow<Float?>(null)
- val effectProgress = _effectProgress.stateIn(bgScope, SharingStarted.Lazily, null)
+ val effectProgress = _effectProgress.asStateFlow()
// Actions to perform
private val _postedActionType = MutableStateFlow<ActionType?>(null)
- val actionType: StateFlow<ActionType?> =
+ val actionType: Flow<ActionType?> =
combine(
- _postedActionType,
- keyguardInteractor.isKeyguardDismissible,
- ) { action, isDismissible ->
- if (!isDismissible && action == ActionType.LONG_PRESS) {
- ActionType.RESET_AND_LONG_PRESS
- } else {
- action
- }
+ _postedActionType,
+ keyguardInteractor.isKeyguardDismissible,
+ ) { action, isDismissible ->
+ if (!isDismissible && action == ActionType.LONG_PRESS) {
+ ActionType.RESET_AND_LONG_PRESS
+ } else {
+ action
}
- .stateIn(bgScope, SharingStarted.Lazily, null)
+ }
// Should a tap timeout countdown begin
val shouldWaitForTapTimeout: Flow<Boolean> = state.map { it == State.TIMEOUT_WAIT }
@@ -129,23 +121,7 @@ constructor(
}
}
- /**
- * Handle relevant touch events for the operation of a Tile.
- *
- * A click action is performed following the relevant logic that originates from the
- * [MotionEvent.ACTION_UP] event depending on the current state.
- */
- @SuppressLint("ClickableViewAccessibility")
- override fun onTouch(view: View?, event: MotionEvent?): Boolean {
- when (event?.actionMasked) {
- MotionEvent.ACTION_DOWN -> handleActionDown()
- MotionEvent.ACTION_UP -> handleActionUp()
- MotionEvent.ACTION_CANCEL -> handleActionCancel()
- }
- return true
- }
-
- private fun handleActionDown() {
+ fun handleActionDown() {
when (_state.value) {
State.IDLE -> {
setState(State.TIMEOUT_WAIT)
@@ -155,7 +131,7 @@ constructor(
}
}
- private fun handleActionUp() {
+ fun handleActionUp() {
when (_state.value) {
State.TIMEOUT_WAIT -> {
_postedActionType.value = ActionType.CLICK
@@ -169,7 +145,7 @@ constructor(
}
}
- private fun handleActionCancel() {
+ fun handleActionCancel() {
when (_state.value) {
State.TIMEOUT_WAIT -> {
setState(State.IDLE)
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
index ddb9f35c74d9..2ef901d34dd1 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
@@ -16,6 +16,8 @@
package com.android.systemui.haptics.qs
+import android.annotation.SuppressLint
+import android.view.MotionEvent
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.tracing.coroutines.launch
@@ -25,10 +27,9 @@ import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.launch
-// TODO(b/332903800)
object QSLongPressEffectViewBinder {
+
fun bind(
tile: QSTileViewImpl,
qsLongPressEffect: QSLongPressEffect?,
@@ -36,11 +37,13 @@ object QSLongPressEffectViewBinder {
): DisposableHandle? {
if (qsLongPressEffect == null) return null
+ // Set the touch listener as the long-press effect
+ setTouchListener(tile, qsLongPressEffect)
+
return tile.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
- val tag = "${tileSpec ?: "unknownTileSpec"}#LongPressEffect"
// Progress of the effect
- launch("$tag#progress") {
+ launch({ "${tileSpec ?: "unknownTileSpec"}#LongPressEffect#progress" }) {
qsLongPressEffect.effectProgress.collect { progress ->
progress?.let {
if (it == 0f) {
@@ -53,7 +56,7 @@ object QSLongPressEffectViewBinder {
}
// Action to perform
- launch("$tag#action") {
+ launch({ "${tileSpec ?: "unknownTileSpec"}#LongPressEffect#action" }) {
qsLongPressEffect.actionType.collect { action ->
action?.let {
when (it) {
@@ -70,7 +73,7 @@ object QSLongPressEffectViewBinder {
}
// Tap timeout wait
- launch("$tag#timeout") {
+ launch({ "${tileSpec ?: "unknownTileSpec"}#LongPressEffect#timeout" }) {
qsLongPressEffect.shouldWaitForTapTimeout
.filter { it }
.collect {
@@ -85,4 +88,16 @@ object QSLongPressEffectViewBinder {
}
}
}
+
+ @SuppressLint("ClickableViewAccessibility")
+ private fun setTouchListener(tile: QSTileViewImpl, longPressEffect: QSLongPressEffect?) {
+ tile.setOnTouchListener { _, event ->
+ when (event.actionMasked) {
+ MotionEvent.ACTION_DOWN -> longPressEffect?.handleActionDown()
+ MotionEvent.ACTION_UP -> longPressEffect?.handleActionUp()
+ MotionEvent.ACTION_CANCEL -> longPressEffect?.handleActionCancel()
+ }
+ true
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index 36896f916e7d..ecad1483ba61 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -27,6 +27,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
@@ -46,6 +47,7 @@ constructor(
val longPress: KeyguardLongPressViewModel,
val shadeInteractor: ShadeInteractor,
@Application private val applicationScope: CoroutineScope,
+ private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
) {
private val clockSize = clockInteractor.clockSize
@@ -75,6 +77,23 @@ constructor(
initialValue = false,
)
+ /** Amount of horizontal translation that should be applied to elements in the scene. */
+ val unfoldTranslations: StateFlow<UnfoldTranslations> =
+ combine(
+ unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = true),
+ unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = false),
+ ) { start, end ->
+ UnfoldTranslations(
+ start = start,
+ end = end,
+ )
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = UnfoldTranslations(),
+ )
+
fun getSmartSpacePaddingTop(resources: Resources): Int {
return if (isLargeClockVisible) {
resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) +
@@ -94,4 +113,20 @@ constructor(
initialValue = interactor.getCurrentBlueprint().id,
)
}
+
+ data class UnfoldTranslations(
+
+ /**
+ * Amount of horizontal translation to apply to elements that are aligned to the start side
+ * (left in left-to-right layouts). Can also be used as horizontal padding for elements that
+ * need horizontal padding on both side. In pixels.
+ */
+ val start: Float = 0f,
+
+ /**
+ * Amount of horizontal translation to apply to elements that are aligned to the end side
+ * (right in left-to-right layouts). In pixels.
+ */
+ val end: Float = 0f,
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index b6098645f7bc..43c73c4e7f35 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -411,18 +411,16 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
@Override
public void setOverrideHomeButtonLongPress(long duration, float slopMultiplier) {
+ Log.d(TAG, "setOverrideHomeButtonLongPress receives: " + duration + "; "
+ + slopMultiplier);
mOverrideHomeButtonLongPressDurationMs = Optional.of(duration)
.filter(value -> value > 0);
mOverrideHomeButtonLongPressSlopMultiplier = Optional.of(slopMultiplier)
.filter(value -> value > 0);
- if (mOverrideHomeButtonLongPressDurationMs.isPresent()) {
- Log.d(TAG, "Receive duration override: "
- + mOverrideHomeButtonLongPressDurationMs.get());
- }
- if (mOverrideHomeButtonLongPressSlopMultiplier.isPresent()) {
- Log.d(TAG, "Receive slop multiplier override: "
- + mOverrideHomeButtonLongPressSlopMultiplier.get());
- }
+ mOverrideHomeButtonLongPressDurationMs.ifPresent(aLong
+ -> Log.d(TAG, "Use duration override: " + aLong));
+ mOverrideHomeButtonLongPressSlopMultiplier.ifPresent(aFloat
+ -> Log.d(TAG, "Use slop multiplier override: " + aFloat));
if (mView != null) {
reconfigureHomeLongClick();
}
@@ -1395,9 +1393,10 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
break;
case MotionEvent.ACTION_MOVE:
if (!mHandler.hasCallbacks(mOnVariableDurationHomeLongClick)) {
- Log.w(TAG, "No callback. Don't handle touch slop.");
+ Log.v(TAG, "ACTION_MOVE no callback. Don't handle touch slop.");
break;
}
+ Log.v(TAG, "ACTION_MOVE handle touch slop");
float customSlopMultiplier = mOverrideHomeButtonLongPressSlopMultiplier.orElse(1f);
float touchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
float calculatedTouchSlop =
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index db4a7fad579c..b50ee576a4c3 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -71,8 +71,6 @@ import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.policy.GestureNavigationSettingsObserver;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.FalsingManager;
@@ -219,10 +217,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
private final Region mExcludeRegion = new Region();
private final Region mDesktopModeExcludeRegion = new Region();
private final Region mUnrestrictedExcludeRegion = new Region();
- private final Provider<NavigationBarEdgePanel> mNavBarEdgePanelProvider;
private final Provider<BackGestureTfClassifierProvider>
mBackGestureTfClassifierProviderProvider;
- private final FeatureFlags mFeatureFlags;
private final Provider<LightBarController> mLightBarControllerProvider;
// The left side edge width where touch down is allowed
@@ -264,8 +260,6 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
private boolean mIsEnabled;
private boolean mIsNavBarShownTransiently;
private boolean mIsBackGestureAllowed;
- private boolean mIsNewBackAffordanceEnabled;
- private boolean mIsTrackpadGestureFeaturesEnabled;
private boolean mIsTrackpadThreeFingerSwipe;
private boolean mIsButtonForcedVisible;
@@ -413,9 +407,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
Optional<Pip> pipOptional,
Optional<DesktopMode> desktopModeOptional,
FalsingManager falsingManager,
- Provider<NavigationBarEdgePanel> navigationBarEdgePanelProvider,
Provider<BackGestureTfClassifierProvider> backGestureTfClassifierProviderProvider,
- FeatureFlags featureFlags,
Provider<LightBarController> lightBarControllerProvider) {
mContext = context;
mDisplayId = context.getDisplayId();
@@ -435,13 +427,9 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
mPipOptional = pipOptional;
mDesktopModeOptional = desktopModeOptional;
mFalsingManager = falsingManager;
- mNavBarEdgePanelProvider = navigationBarEdgePanelProvider;
mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider;
- mFeatureFlags = featureFlags;
mLightBarControllerProvider = lightBarControllerProvider;
mLastReportedConfig.setTo(mContext.getResources().getConfiguration());
- mIsTrackpadGestureFeaturesEnabled = mFeatureFlags.isEnabled(
- Flags.TRACKPAD_GESTURE_FEATURES);
ComponentName recentsComponentName = ComponentName.unflattenFromString(
context.getString(com.android.internal.R.string.config_recentsComponentName));
if (recentsComponentName != null) {
@@ -559,12 +547,10 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
mIsAttached = true;
mOverviewProxyService.addCallback(mQuickSwitchListener);
mSysUiState.addCallback(mSysUiStateCallback);
- if (mIsTrackpadGestureFeaturesEnabled) {
- mInputManager.registerInputDeviceListener(mInputDeviceListener, mMainHandler);
- int [] inputDevices = mInputManager.getInputDeviceIds();
- for (int inputDeviceId : inputDevices) {
- mInputDeviceListener.onInputDeviceAdded(inputDeviceId);
- }
+ mInputManager.registerInputDeviceListener(mInputDeviceListener, mMainHandler);
+ int [] inputDevices = mInputManager.getInputDeviceIds();
+ for (int inputDeviceId : inputDevices) {
+ mInputDeviceListener.onInputDeviceAdded(inputDeviceId);
}
updateIsEnabled();
mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
@@ -616,9 +602,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
try {
Trace.beginSection("EdgeBackGestureHandler#updateIsEnabled");
- mIsGestureHandlingEnabled =
- mInGestureNavMode || (mIsTrackpadGestureFeaturesEnabled && mUsingThreeButtonNav
- && mIsTrackpadConnected);
+ mIsGestureHandlingEnabled = mInGestureNavMode || (mUsingThreeButtonNav
+ && mIsTrackpadConnected);
boolean isEnabled = mIsAttached && mIsGestureHandlingEnabled;
if (isEnabled == mIsEnabled) {
return;
@@ -678,7 +663,6 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
Choreographer.getInstance(), this::onInputEvent);
// Add a nav bar panel window
- mIsNewBackAffordanceEnabled = mFeatureFlags.isEnabled(Flags.NEW_BACK_AFFORDANCE);
resetEdgeBackPlugin();
mPluginManager.addPluginListener(
this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false);
@@ -701,12 +685,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
}
private void resetEdgeBackPlugin() {
- if (mIsNewBackAffordanceEnabled) {
- setEdgeBackPlugin(
- mBackPanelControllerFactory.create(mContext));
- } else {
- setEdgeBackPlugin(mNavBarEdgePanelProvider.get());
- }
+ setEdgeBackPlugin(mBackPanelControllerFactory.create(mContext));
}
private void setEdgeBackPlugin(NavigationEdgeBackPlugin edgeBackPlugin) {
@@ -1001,8 +980,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
Log.d(DEBUG_MISSING_GESTURE_TAG, "Start gesture: " + ev);
}
- mIsTrackpadThreeFingerSwipe = isTrackpadThreeFingerSwipe(
- mIsTrackpadGestureFeaturesEnabled, ev);
+ mIsTrackpadThreeFingerSwipe = isTrackpadThreeFingerSwipe(ev);
// ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new
// ACTION_DOWN, in that case we should just reuse the old instance.
@@ -1027,7 +1005,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
&& !mGestureBlockingActivityRunning.get()
&& !QuickStepContract.isBackGestureDisabled(mSysUiFlags,
mIsTrackpadThreeFingerSwipe)
- && !isTrackpadScroll(mIsTrackpadGestureFeaturesEnabled, ev);
+ && !isTrackpadScroll(ev);
if (mIsTrackpadThreeFingerSwipe) {
// Trackpad back gestures don't have zones, so we don't need to check if the down
// event is within insets.
@@ -1321,10 +1299,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
private final Optional<Pip> mPipOptional;
private final Optional<DesktopMode> mDesktopModeOptional;
private final FalsingManager mFalsingManager;
- private final Provider<NavigationBarEdgePanel> mNavBarEdgePanelProvider;
private final Provider<BackGestureTfClassifierProvider>
mBackGestureTfClassifierProviderProvider;
- private final FeatureFlags mFeatureFlags;
private final Provider<LightBarController> mLightBarControllerProvider;
@Inject
@@ -1344,10 +1320,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
Optional<Pip> pipOptional,
Optional<DesktopMode> desktopModeOptional,
FalsingManager falsingManager,
- Provider<NavigationBarEdgePanel> navBarEdgePanelProvider,
Provider<BackGestureTfClassifierProvider>
backGestureTfClassifierProviderProvider,
- FeatureFlags featureFlags,
Provider<LightBarController> lightBarControllerProvider) {
mOverviewProxyService = overviewProxyService;
mSysUiState = sysUiState;
@@ -1365,9 +1339,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
mPipOptional = pipOptional;
mDesktopModeOptional = desktopModeOptional;
mFalsingManager = falsingManager;
- mNavBarEdgePanelProvider = navBarEdgePanelProvider;
mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider;
- mFeatureFlags = featureFlags;
mLightBarControllerProvider = lightBarControllerProvider;
}
@@ -1391,9 +1363,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
mPipOptional,
mDesktopModeOptional,
mFalsingManager,
- mNavBarEdgePanelProvider,
mBackGestureTfClassifierProviderProvider,
- mFeatureFlags,
mLightBarControllerProvider);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
deleted file mode 100644
index 380846e17a13..000000000000
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ /dev/null
@@ -1,944 +0,0 @@
-/*
- * Copyright (C) 2020 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.navigationbar.gestural;
-
-import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE;
-import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE_TAG;
-
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.os.VibrationEffect;
-import android.util.Log;
-import android.util.MathUtils;
-import android.view.ContextThemeWrapper;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.animation.Interpolator;
-import android.view.animation.PathInterpolator;
-
-import androidx.core.graphics.ColorUtils;
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.dynamicanimation.animation.FloatPropertyCompat;
-import androidx.dynamicanimation.animation.SpringAnimation;
-import androidx.dynamicanimation.animation.SpringForce;
-
-import com.android.app.animation.Interpolators;
-import com.android.internal.util.LatencyTracker;
-import com.android.settingslib.Utils;
-import com.android.systemui.res.R;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.plugins.NavigationEdgeBackPlugin;
-import com.android.systemui.settings.DisplayTracker;
-import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
-import com.android.systemui.statusbar.VibratorHelper;
-
-import java.io.PrintWriter;
-import java.util.concurrent.Executor;
-
-import javax.inject.Inject;
-
-public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPlugin {
-
- private static final String TAG = "NavigationBarEdgePanel";
-
- private static final boolean ENABLE_FAILSAFE = true;
-
- private static final long COLOR_ANIMATION_DURATION_MS = 120;
- private static final long DISAPPEAR_FADE_ANIMATION_DURATION_MS = 80;
- private static final long DISAPPEAR_ARROW_ANIMATION_DURATION_MS = 100;
- private static final long FAILSAFE_DELAY_MS = 200;
-
- /**
- * The time required since the first vibration effect to automatically trigger a click
- */
- private static final int GESTURE_DURATION_FOR_CLICK_MS = 400;
-
- /**
- * The size of the protection of the arrow in px. Only used if this is not background protected
- */
- private static final int PROTECTION_WIDTH_PX = 2;
-
- /**
- * The basic translation in dp where the arrow resides
- */
- private static final int BASE_TRANSLATION_DP = 32;
-
- /**
- * The length of the arrow leg measured from the center to the end
- */
- private static final int ARROW_LENGTH_DP = 18;
-
- /**
- * The angle measured from the xAxis, where the leg is when the arrow rests
- */
- private static final int ARROW_ANGLE_WHEN_EXTENDED_DEGREES = 56;
-
- /**
- * The angle that is added per 1000 px speed to the angle of the leg
- */
- private static final int ARROW_ANGLE_ADDED_PER_1000_SPEED = 4;
-
- /**
- * The maximum angle offset allowed due to speed
- */
- private static final int ARROW_MAX_ANGLE_SPEED_OFFSET_DEGREES = 4;
-
- /**
- * The thickness of the arrow. Adjusted to match the home handle (approximately)
- */
- private static final float ARROW_THICKNESS_DP = 2.5f;
-
- /**
- * The amount of rubber banding we do for the vertical translation
- */
- private static final int RUBBER_BAND_AMOUNT = 15;
-
- /**
- * The interpolator used to rubberband
- */
- private static final Interpolator RUBBER_BAND_INTERPOLATOR
- = new PathInterpolator(1.0f / 5.0f, 1.0f, 1.0f, 1.0f);
-
- /**
- * The amount of rubber banding we do for the translation before base translation
- */
- private static final int RUBBER_BAND_AMOUNT_APPEAR = 4;
-
- /**
- * The interpolator used to rubberband the appearing of the arrow.
- */
- private static final Interpolator RUBBER_BAND_INTERPOLATOR_APPEAR
- = new PathInterpolator(1.0f / RUBBER_BAND_AMOUNT_APPEAR, 1.0f, 1.0f, 1.0f);
-
- private final WindowManager mWindowManager;
- private final VibratorHelper mVibratorHelper;
-
- /**
- * The paint the arrow is drawn with
- */
- private final Paint mPaint = new Paint();
- /**
- * The paint the arrow protection is drawn with
- */
- private final Paint mProtectionPaint;
-
- private final float mDensity;
- private final float mBaseTranslation;
- private final float mArrowLength;
- private final float mArrowThickness;
-
- /**
- * The minimum delta needed in movement for the arrow to change direction / stop triggering back
- */
- private final float mMinDeltaForSwitch;
- // The closest to y = 0 that the arrow will be displayed.
- private int mMinArrowPosition;
- // The amount the arrow is shifted to avoid the finger.
- private int mFingerOffset;
-
- private final float mSwipeTriggerThreshold;
- private final float mSwipeProgressThreshold;
- private final Path mArrowPath = new Path();
- private final Point mDisplaySize = new Point();
-
- private final SpringAnimation mAngleAnimation;
- private final SpringAnimation mTranslationAnimation;
- private final SpringAnimation mVerticalTranslationAnimation;
- private final SpringForce mAngleAppearForce;
- private final SpringForce mAngleDisappearForce;
- private final ValueAnimator mArrowColorAnimator;
- private final ValueAnimator mArrowDisappearAnimation;
- private final SpringForce mRegularTranslationSpring;
- private final SpringForce mTriggerBackSpring;
- private final LatencyTracker mLatencyTracker;
-
- private VelocityTracker mVelocityTracker;
- private boolean mIsDark = false;
- private boolean mShowProtection = false;
- private int mProtectionColorLight;
- private int mArrowPaddingEnd;
- private int mArrowColorLight;
- private int mProtectionColorDark;
- private int mArrowColorDark;
- private int mProtectionColor;
- private int mArrowColor;
- private RegionSamplingHelper mRegionSamplingHelper;
- private final Rect mSamplingRect = new Rect();
- private WindowManager.LayoutParams mLayoutParams;
- private int mLeftInset;
- private int mRightInset;
-
- /**
- * True if the panel is currently on the left of the screen
- */
- private boolean mIsLeftPanel;
-
- private float mStartX;
- private float mStartY;
- private float mCurrentAngle;
- /**
- * The current translation of the arrow
- */
- private float mCurrentTranslation;
- /**
- * Where the arrow will be in the resting position.
- */
- private float mDesiredTranslation;
-
- private boolean mDragSlopPassed;
- private boolean mArrowsPointLeft;
- private float mMaxTranslation;
- private boolean mTriggerBack;
- private float mPreviousTouchTranslation;
- private float mTotalTouchDelta;
- private float mVerticalTranslation;
- private float mDesiredVerticalTranslation;
- private float mDesiredAngle;
- private float mAngleOffset;
- private int mArrowStartColor;
- private int mCurrentArrowColor;
- private float mDisappearAmount;
- private long mVibrationTime;
- private int mScreenSize;
- private boolean mTrackingBackArrowLatency = false;
-
- private final Handler mHandler = new Handler();
- private final Runnable mFailsafeRunnable = this::onFailsafe;
-
- private DynamicAnimation.OnAnimationEndListener mSetGoneEndListener
- = new DynamicAnimation.OnAnimationEndListener() {
- @Override
- public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
- float velocity) {
- animation.removeEndListener(this);
- if (!canceled) {
- setVisibility(GONE);
- }
- }
- };
- private static final FloatPropertyCompat<NavigationBarEdgePanel> CURRENT_ANGLE =
- new FloatPropertyCompat<NavigationBarEdgePanel>("currentAngle") {
- @Override
- public void setValue(NavigationBarEdgePanel object, float value) {
- object.setCurrentAngle(value);
- }
-
- @Override
- public float getValue(NavigationBarEdgePanel object) {
- return object.getCurrentAngle();
- }
- };
-
- private static final FloatPropertyCompat<NavigationBarEdgePanel> CURRENT_TRANSLATION =
- new FloatPropertyCompat<NavigationBarEdgePanel>("currentTranslation") {
-
- @Override
- public void setValue(NavigationBarEdgePanel object, float value) {
- object.setCurrentTranslation(value);
- }
-
- @Override
- public float getValue(NavigationBarEdgePanel object) {
- return object.getCurrentTranslation();
- }
- };
- private static final FloatPropertyCompat<NavigationBarEdgePanel> CURRENT_VERTICAL_TRANSLATION =
- new FloatPropertyCompat<NavigationBarEdgePanel>("verticalTranslation") {
-
- @Override
- public void setValue(NavigationBarEdgePanel object, float value) {
- object.setVerticalTranslation(value);
- }
-
- @Override
- public float getValue(NavigationBarEdgePanel object) {
- return object.getVerticalTranslation();
- }
- };
- private BackCallback mBackCallback;
-
- @Inject
- public NavigationBarEdgePanel(
- Context context,
- LatencyTracker latencyTracker,
- VibratorHelper vibratorHelper,
- @Background Executor backgroundExecutor,
- DisplayTracker displayTracker) {
- super(context);
-
- mWindowManager = context.getSystemService(WindowManager.class);
- mVibratorHelper = vibratorHelper;
-
- mDensity = context.getResources().getDisplayMetrics().density;
-
- mBaseTranslation = dp(BASE_TRANSLATION_DP);
- mArrowLength = dp(ARROW_LENGTH_DP);
- mArrowThickness = dp(ARROW_THICKNESS_DP);
- mMinDeltaForSwitch = dp(32);
-
- mPaint.setStrokeWidth(mArrowThickness);
- mPaint.setStrokeCap(Paint.Cap.ROUND);
- mPaint.setAntiAlias(true);
- mPaint.setStyle(Paint.Style.STROKE);
- mPaint.setStrokeJoin(Paint.Join.ROUND);
-
- mArrowColorAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
- mArrowColorAnimator.setDuration(COLOR_ANIMATION_DURATION_MS);
- mArrowColorAnimator.addUpdateListener(animation -> {
- int newColor = ColorUtils.blendARGB(
- mArrowStartColor, mArrowColor, animation.getAnimatedFraction());
- setCurrentArrowColor(newColor);
- });
-
- mArrowDisappearAnimation = ValueAnimator.ofFloat(0.0f, 1.0f);
- mArrowDisappearAnimation.setDuration(DISAPPEAR_ARROW_ANIMATION_DURATION_MS);
- mArrowDisappearAnimation.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- mArrowDisappearAnimation.addUpdateListener(animation -> {
- mDisappearAmount = (float) animation.getAnimatedValue();
- invalidate();
- });
-
- mAngleAnimation =
- new SpringAnimation(this, CURRENT_ANGLE);
- mAngleAppearForce = new SpringForce()
- .setStiffness(500)
- .setDampingRatio(0.5f);
- mAngleDisappearForce = new SpringForce()
- .setStiffness(SpringForce.STIFFNESS_MEDIUM)
- .setDampingRatio(SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)
- .setFinalPosition(90);
- mAngleAnimation.setSpring(mAngleAppearForce).setMaxValue(90);
-
- mTranslationAnimation =
- new SpringAnimation(this, CURRENT_TRANSLATION);
- mRegularTranslationSpring = new SpringForce()
- .setStiffness(SpringForce.STIFFNESS_MEDIUM)
- .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
- mTriggerBackSpring = new SpringForce()
- .setStiffness(450)
- .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
- mTranslationAnimation.setSpring(mRegularTranslationSpring);
- mVerticalTranslationAnimation =
- new SpringAnimation(this, CURRENT_VERTICAL_TRANSLATION);
- mVerticalTranslationAnimation.setSpring(
- new SpringForce()
- .setStiffness(SpringForce.STIFFNESS_MEDIUM)
- .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
-
- mProtectionPaint = new Paint(mPaint);
- mProtectionPaint.setStrokeWidth(mArrowThickness + PROTECTION_WIDTH_PX);
- loadDimens();
-
- loadColors(context);
- updateArrowDirection();
-
- mSwipeTriggerThreshold = context.getResources()
- .getDimension(R.dimen.navigation_edge_action_drag_threshold);
- mSwipeProgressThreshold = context.getResources()
- .getDimension(R.dimen.navigation_edge_action_progress_threshold);
-
- setVisibility(GONE);
-
- boolean isPrimaryDisplay = mContext.getDisplayId() == displayTracker.getDefaultDisplayId();
- mRegionSamplingHelper = new RegionSamplingHelper(this,
- new RegionSamplingHelper.SamplingCallback() {
- @Override
- public void onRegionDarknessChanged(boolean isRegionDark) {
- setIsDark(!isRegionDark, true /* animate */);
- }
-
- @Override
- public Rect getSampledRegion(View sampledView) {
- return mSamplingRect;
- }
-
- @Override
- public boolean isSamplingEnabled() {
- return isPrimaryDisplay;
- }
- }, backgroundExecutor);
- mRegionSamplingHelper.setWindowVisible(true);
- mShowProtection = !isPrimaryDisplay;
- mLatencyTracker = latencyTracker;
- }
-
- @Override
- public void onDestroy() {
- cancelFailsafe();
- mWindowManager.removeView(this);
- mRegionSamplingHelper.stop();
- mRegionSamplingHelper = null;
- }
-
- @Override
- public boolean hasOverlappingRendering() {
- return false;
- }
-
- private void setIsDark(boolean isDark, boolean animate) {
- mIsDark = isDark;
- updateIsDark(animate);
- }
-
- @Override
- public void setIsLeftPanel(boolean isLeftPanel) {
- mIsLeftPanel = isLeftPanel;
- mLayoutParams.gravity = mIsLeftPanel
- ? (Gravity.LEFT | Gravity.TOP)
- : (Gravity.RIGHT | Gravity.TOP);
- }
-
- @Override
- public void setInsets(int leftInset, int rightInset) {
- mLeftInset = leftInset;
- mRightInset = rightInset;
- }
-
- @Override
- public void setDisplaySize(Point displaySize) {
- mDisplaySize.set(displaySize.x, displaySize.y);
- mScreenSize = Math.min(mDisplaySize.x, mDisplaySize.y);
- }
-
- @Override
- public void setBackCallback(BackCallback callback) {
- mBackCallback = callback;
- }
-
- @Override
- public void setLayoutParams(WindowManager.LayoutParams layoutParams) {
- mLayoutParams = layoutParams;
- mWindowManager.addView(this, mLayoutParams);
- }
-
- /**
- * Adjusts the sampling rect to conform to the actual visible bounding box of the arrow.
- */
- private void adjustSamplingRectToBoundingBox() {
- float translation = mDesiredTranslation;
- if (!mTriggerBack) {
- // Let's take the resting position and bounds as the sampling rect, since we are not
- // visible right now
- translation = mBaseTranslation;
- if (mIsLeftPanel && mArrowsPointLeft
- || (!mIsLeftPanel && !mArrowsPointLeft)) {
- // If we're on the left we should move less, because the arrow is facing the other
- // direction
- translation -= getStaticArrowWidth();
- }
- }
- float left = translation - mArrowThickness / 2.0f;
- left = mIsLeftPanel ? left : mSamplingRect.width() - left;
-
- // Let's calculate the position of the end based on the angle
- float width = getStaticArrowWidth();
- float height = polarToCartY(ARROW_ANGLE_WHEN_EXTENDED_DEGREES) * mArrowLength * 2.0f;
- if (!mArrowsPointLeft) {
- left -= width;
- }
-
- float top = (getHeight() * 0.5f) + mDesiredVerticalTranslation - height / 2.0f;
- mSamplingRect.offset((int) left, (int) top);
- mSamplingRect.set(mSamplingRect.left, mSamplingRect.top,
- (int) (mSamplingRect.left + width),
- (int) (mSamplingRect.top + height));
- mRegionSamplingHelper.updateSamplingRect();
- }
-
- @Override
- public void onMotionEvent(MotionEvent event) {
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- mVelocityTracker.addMovement(event);
- switch (event.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- mDragSlopPassed = false;
- resetOnDown();
- mStartX = event.getX();
- mStartY = event.getY();
- setVisibility(VISIBLE);
- updatePosition(event.getY());
- mRegionSamplingHelper.start(mSamplingRect);
- mWindowManager.updateViewLayout(this, mLayoutParams);
- mLatencyTracker.onActionStart(LatencyTracker.ACTION_SHOW_BACK_ARROW);
- mTrackingBackArrowLatency = true;
- break;
- case MotionEvent.ACTION_MOVE:
- handleMoveEvent(event);
- break;
- case MotionEvent.ACTION_UP:
- if (DEBUG_MISSING_GESTURE) {
- Log.d(DEBUG_MISSING_GESTURE_TAG,
- "NavigationBarEdgePanel ACTION_UP, mTriggerBack=" + mTriggerBack);
- }
- if (mTriggerBack) {
- triggerBack();
- } else {
- cancelBack();
- }
- mRegionSamplingHelper.stop();
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- break;
- case MotionEvent.ACTION_CANCEL:
- if (DEBUG_MISSING_GESTURE) {
- Log.d(DEBUG_MISSING_GESTURE_TAG, "NavigationBarEdgePanel ACTION_CANCEL");
- }
- cancelBack();
- mRegionSamplingHelper.stop();
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- break;
- }
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- updateArrowDirection();
- loadDimens();
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- float pointerPosition = mCurrentTranslation - mArrowThickness / 2.0f;
- canvas.save();
- canvas.translate(
- mIsLeftPanel ? pointerPosition : getWidth() - pointerPosition,
- (getHeight() * 0.5f) + mVerticalTranslation);
-
- // Let's calculate the position of the end based on the angle
- float x = (polarToCartX(mCurrentAngle) * mArrowLength);
- float y = (polarToCartY(mCurrentAngle) * mArrowLength);
- Path arrowPath = calculatePath(x,y);
- if (mShowProtection) {
- canvas.drawPath(arrowPath, mProtectionPaint);
- }
-
- canvas.drawPath(arrowPath, mPaint);
- canvas.restore();
- if (mTrackingBackArrowLatency) {
- mLatencyTracker.onActionEnd(LatencyTracker.ACTION_SHOW_BACK_ARROW);
- mTrackingBackArrowLatency = false;
- }
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
-
- mMaxTranslation = getWidth() - mArrowPaddingEnd;
- }
-
- private void loadDimens() {
- Resources res = getResources();
- mArrowPaddingEnd = res.getDimensionPixelSize(R.dimen.navigation_edge_panel_padding);
- mMinArrowPosition = res.getDimensionPixelSize(R.dimen.navigation_edge_arrow_min_y);
- mFingerOffset = res.getDimensionPixelSize(R.dimen.navigation_edge_finger_offset);
- }
-
- private void updateArrowDirection() {
- // Both panels arrow point the same way
- mArrowsPointLeft = getLayoutDirection() == LAYOUT_DIRECTION_LTR;
- invalidate();
- }
-
- private void loadColors(Context context) {
- final int dualToneDarkTheme = Utils.getThemeAttr(context, R.attr.darkIconTheme);
- final int dualToneLightTheme = Utils.getThemeAttr(context, R.attr.lightIconTheme);
- Context lightContext = new ContextThemeWrapper(context, dualToneLightTheme);
- Context darkContext = new ContextThemeWrapper(context, dualToneDarkTheme);
- mArrowColorLight = Utils.getColorAttrDefaultColor(lightContext, R.attr.singleToneColor);
- mArrowColorDark = Utils.getColorAttrDefaultColor(darkContext, R.attr.singleToneColor);
- mProtectionColorDark = mArrowColorLight;
- mProtectionColorLight = mArrowColorDark;
- updateIsDark(false /* animate */);
- }
-
- private void updateIsDark(boolean animate) {
- // TODO: Maybe animate protection as well
- mProtectionColor = mIsDark ? mProtectionColorDark : mProtectionColorLight;
- mProtectionPaint.setColor(mProtectionColor);
- mArrowColor = mIsDark ? mArrowColorDark : mArrowColorLight;
- mArrowColorAnimator.cancel();
- if (!animate) {
- setCurrentArrowColor(mArrowColor);
- } else {
- mArrowStartColor = mCurrentArrowColor;
- mArrowColorAnimator.start();
- }
- }
-
- private void setCurrentArrowColor(int color) {
- mCurrentArrowColor = color;
- mPaint.setColor(color);
- invalidate();
- }
-
- private float getStaticArrowWidth() {
- return polarToCartX(ARROW_ANGLE_WHEN_EXTENDED_DEGREES) * mArrowLength;
- }
-
- private float polarToCartX(float angleInDegrees) {
- return (float) Math.cos(Math.toRadians(angleInDegrees));
- }
-
- private float polarToCartY(float angleInDegrees) {
- return (float) Math.sin(Math.toRadians(angleInDegrees));
- }
-
- private Path calculatePath(float x, float y) {
- if (!mArrowsPointLeft) {
- x = -x;
- }
- float extent = MathUtils.lerp(1.0f, 0.75f, mDisappearAmount);
- x = x * extent;
- y = y * extent;
- mArrowPath.reset();
- mArrowPath.moveTo(x, y);
- mArrowPath.lineTo(0, 0);
- mArrowPath.lineTo(x, -y);
- return mArrowPath;
- }
-
- private float getCurrentAngle() {
- return mCurrentAngle;
- }
-
- private float getCurrentTranslation() {
- return mCurrentTranslation;
- }
-
- private void triggerBack() {
- mBackCallback.triggerBack();
-
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- mVelocityTracker.computeCurrentVelocity(1000);
- // Only do the extra translation if we're not already flinging
- boolean isSlow = Math.abs(mVelocityTracker.getXVelocity()) < 500;
- if (isSlow
- || SystemClock.uptimeMillis() - mVibrationTime >= GESTURE_DURATION_FOR_CLICK_MS) {
- mVibratorHelper.vibrate(VibrationEffect.EFFECT_CLICK);
- }
-
- // Let's also snap the angle a bit
- if (mAngleOffset > -4) {
- mAngleOffset = Math.max(-8, mAngleOffset - 8);
- updateAngle(true /* animated */);
- }
-
- // Finally, after the translation, animate back and disappear the arrow
- Runnable translationEnd = () -> {
- // let's snap it back
- mAngleOffset = Math.max(0, mAngleOffset + 8);
- updateAngle(true /* animated */);
-
- mTranslationAnimation.setSpring(mTriggerBackSpring);
- // Translate the arrow back a bit to make for a nice transition
- setDesiredTranslation(mDesiredTranslation - dp(32), true /* animated */);
- animate().alpha(0f).setDuration(DISAPPEAR_FADE_ANIMATION_DURATION_MS)
- .withEndAction(() -> setVisibility(GONE));
- mArrowDisappearAnimation.start();
- // Schedule failsafe in case alpha end callback is not called
- scheduleFailsafe();
- };
- if (mTranslationAnimation.isRunning()) {
- mTranslationAnimation.addEndListener(new DynamicAnimation.OnAnimationEndListener() {
- @Override
- public void onAnimationEnd(DynamicAnimation animation, boolean canceled,
- float value,
- float velocity) {
- animation.removeEndListener(this);
- if (!canceled) {
- translationEnd.run();
- }
- }
- });
- // Schedule failsafe in case mTranslationAnimation end callback is not called
- scheduleFailsafe();
- } else {
- translationEnd.run();
- }
- }
-
- private void cancelBack() {
- mBackCallback.cancelBack();
-
- if (mTranslationAnimation.isRunning()) {
- mTranslationAnimation.addEndListener(mSetGoneEndListener);
- // Schedule failsafe in case mTranslationAnimation end callback is not called
- scheduleFailsafe();
- } else {
- setVisibility(GONE);
- }
- }
-
- private void resetOnDown() {
- animate().cancel();
- mAngleAnimation.cancel();
- mTranslationAnimation.cancel();
- mVerticalTranslationAnimation.cancel();
- mArrowDisappearAnimation.cancel();
- mAngleOffset = 0;
- mTranslationAnimation.setSpring(mRegularTranslationSpring);
- // Reset the arrow to the side
- if (DEBUG_MISSING_GESTURE) {
- Log.d(DEBUG_MISSING_GESTURE_TAG, "reset mTriggerBack=false");
- }
- setTriggerBack(false /* triggerBack */, false /* animated */);
- setDesiredTranslation(0, false /* animated */);
- setCurrentTranslation(0);
- updateAngle(false /* animate */);
- mPreviousTouchTranslation = 0;
- mTotalTouchDelta = 0;
- mVibrationTime = 0;
- setDesiredVerticalTransition(0, false /* animated */);
- cancelFailsafe();
- }
-
- private void handleMoveEvent(MotionEvent event) {
- float x = event.getX();
- float y = event.getY();
- float touchTranslation = MathUtils.abs(x - mStartX);
- float yOffset = y - mStartY;
- float delta = touchTranslation - mPreviousTouchTranslation;
- if (Math.abs(delta) > 0) {
- if (Math.signum(delta) == Math.signum(mTotalTouchDelta)) {
- mTotalTouchDelta += delta;
- } else {
- mTotalTouchDelta = delta;
- }
- }
- mPreviousTouchTranslation = touchTranslation;
-
- // Apply a haptic on drag slop passed
- if (!mDragSlopPassed && touchTranslation > mSwipeTriggerThreshold) {
- mDragSlopPassed = true;
- mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
- mVibrationTime = SystemClock.uptimeMillis();
-
- // Let's show the arrow and animate it in!
- mDisappearAmount = 0.0f;
- setAlpha(1f);
- // And animate it go to back by default!
- if (DEBUG_MISSING_GESTURE) {
- Log.d(DEBUG_MISSING_GESTURE_TAG, "set mTriggerBack=true");
- }
- setTriggerBack(true /* triggerBack */, true /* animated */);
- }
-
- // Let's make sure we only go to the baseextend and apply rubberbanding afterwards
- if (touchTranslation > mBaseTranslation) {
- float diff = touchTranslation - mBaseTranslation;
- float progress = MathUtils.saturate(diff / (mScreenSize - mBaseTranslation));
- progress = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress)
- * (mMaxTranslation - mBaseTranslation);
- touchTranslation = mBaseTranslation + progress;
- } else {
- float diff = mBaseTranslation - touchTranslation;
- float progress = MathUtils.saturate(diff / mBaseTranslation);
- progress = RUBBER_BAND_INTERPOLATOR_APPEAR.getInterpolation(progress)
- * (mBaseTranslation / RUBBER_BAND_AMOUNT_APPEAR);
- touchTranslation = mBaseTranslation - progress;
- }
- // By default we just assume the current direction is kept
- boolean triggerBack = mTriggerBack;
-
- // First lets see if we had continuous motion in one direction for a while
- if (Math.abs(mTotalTouchDelta) > mMinDeltaForSwitch) {
- triggerBack = mTotalTouchDelta > 0;
- }
-
- // Then, let's see if our velocity tells us to change direction
- mVelocityTracker.computeCurrentVelocity(1000);
- float xVelocity = mVelocityTracker.getXVelocity();
- float yVelocity = mVelocityTracker.getYVelocity();
- float velocity = MathUtils.mag(xVelocity, yVelocity);
- mAngleOffset = Math.min(velocity / 1000 * ARROW_ANGLE_ADDED_PER_1000_SPEED,
- ARROW_MAX_ANGLE_SPEED_OFFSET_DEGREES) * Math.signum(xVelocity);
- if (mIsLeftPanel && mArrowsPointLeft || !mIsLeftPanel && !mArrowsPointLeft) {
- mAngleOffset *= -1;
- }
-
- // Last if the direction in Y is bigger than X * 2 we also abort
- if (Math.abs(yOffset) > Math.abs(x - mStartX) * 2) {
- triggerBack = false;
- }
- if (DEBUG_MISSING_GESTURE && mTriggerBack != triggerBack) {
- Log.d(DEBUG_MISSING_GESTURE_TAG, "set mTriggerBack=" + triggerBack
- + ", mTotalTouchDelta=" + mTotalTouchDelta
- + ", mMinDeltaForSwitch=" + mMinDeltaForSwitch
- + ", yOffset=" + yOffset
- + ", x=" + x
- + ", mStartX=" + mStartX);
- }
- setTriggerBack(triggerBack, true /* animated */);
-
- if (!mTriggerBack) {
- touchTranslation = 0;
- } else if (mIsLeftPanel && mArrowsPointLeft
- || (!mIsLeftPanel && !mArrowsPointLeft)) {
- // If we're on the left we should move less, because the arrow is facing the other
- // direction
- touchTranslation -= getStaticArrowWidth();
- }
- setDesiredTranslation(touchTranslation, true /* animated */);
- updateAngle(true /* animated */);
-
- float maxYOffset = getHeight() / 2.0f - mArrowLength;
- float progress = MathUtils.constrain(
- Math.abs(yOffset) / (maxYOffset * RUBBER_BAND_AMOUNT),
- 0, 1);
- float verticalTranslation = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress)
- * maxYOffset * Math.signum(yOffset);
- setDesiredVerticalTransition(verticalTranslation, true /* animated */);
- updateSamplingRect();
- }
-
- private void updatePosition(float touchY) {
- float position = touchY - mFingerOffset;
- position = Math.max(position, mMinArrowPosition);
- position -= mLayoutParams.height / 2.0f;
- mLayoutParams.y = MathUtils.constrain((int) position, 0, mDisplaySize.y);
- updateSamplingRect();
- }
-
- private void updateSamplingRect() {
- int top = mLayoutParams.y;
- int left = mIsLeftPanel ? mLeftInset : mDisplaySize.x - mRightInset - mLayoutParams.width;
- int right = left + mLayoutParams.width;
- int bottom = top + mLayoutParams.height;
- mSamplingRect.set(left, top, right, bottom);
- adjustSamplingRectToBoundingBox();
- }
-
- private void setDesiredVerticalTransition(float verticalTranslation, boolean animated) {
- if (mDesiredVerticalTranslation != verticalTranslation) {
- mDesiredVerticalTranslation = verticalTranslation;
- if (!animated) {
- setVerticalTranslation(verticalTranslation);
- } else {
- mVerticalTranslationAnimation.animateToFinalPosition(verticalTranslation);
- }
- invalidate();
- }
- }
-
- private void setVerticalTranslation(float verticalTranslation) {
- mVerticalTranslation = verticalTranslation;
- invalidate();
- }
-
- private float getVerticalTranslation() {
- return mVerticalTranslation;
- }
-
- private void setDesiredTranslation(float desiredTranslation, boolean animated) {
- if (mDesiredTranslation != desiredTranslation) {
- mDesiredTranslation = desiredTranslation;
- if (!animated) {
- setCurrentTranslation(desiredTranslation);
- } else {
- mTranslationAnimation.animateToFinalPosition(desiredTranslation);
- }
- }
- }
-
- private void setCurrentTranslation(float currentTranslation) {
- mCurrentTranslation = currentTranslation;
- invalidate();
- }
-
- private void setTriggerBack(boolean triggerBack, boolean animated) {
- if (mTriggerBack != triggerBack) {
- mTriggerBack = triggerBack;
- mAngleAnimation.cancel();
- updateAngle(animated);
- // Whenever the trigger back state changes the existing translation animation should be
- // cancelled
- mTranslationAnimation.cancel();
- mBackCallback.setTriggerBack(mTriggerBack);
- }
- }
-
- private void updateAngle(boolean animated) {
- float newAngle = mTriggerBack ? ARROW_ANGLE_WHEN_EXTENDED_DEGREES + mAngleOffset : 90;
- if (newAngle != mDesiredAngle) {
- if (!animated) {
- setCurrentAngle(newAngle);
- } else {
- mAngleAnimation.setSpring(mTriggerBack ? mAngleAppearForce : mAngleDisappearForce);
- mAngleAnimation.animateToFinalPosition(newAngle);
- }
- mDesiredAngle = newAngle;
- }
- }
-
- private void setCurrentAngle(float currentAngle) {
- mCurrentAngle = currentAngle;
- invalidate();
- }
-
- private void scheduleFailsafe() {
- if (!ENABLE_FAILSAFE) {
- return;
- }
- cancelFailsafe();
- mHandler.postDelayed(mFailsafeRunnable, FAILSAFE_DELAY_MS);
- }
-
- private void cancelFailsafe() {
- mHandler.removeCallbacks(mFailsafeRunnable);
- }
-
- private void onFailsafe() {
- setVisibility(GONE);
- }
-
- private float dp(float dp) {
- return mDensity * dp;
- }
-
- @Override
- public void dump(PrintWriter pw) {
- pw.println("NavigationBarEdgePanel:");
- pw.println(" mIsLeftPanel=" + mIsLeftPanel);
- pw.println(" mTriggerBack=" + mTriggerBack);
- pw.println(" mDragSlopPassed=" + mDragSlopPassed);
- pw.println(" mCurrentAngle=" + mCurrentAngle);
- pw.println(" mDesiredAngle=" + mDesiredAngle);
- pw.println(" mCurrentTranslation=" + mCurrentTranslation);
- pw.println(" mDesiredTranslation=" + mDesiredTranslation);
- pw.println(" mTranslationAnimation running=" + mTranslationAnimation.isRunning());
- mRegionSamplingHelper.dump(pw);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
index 10a88c8b4839..b46f2d2f5d62 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
@@ -24,16 +24,12 @@ import android.view.MotionEvent;
public final class Utilities {
- public static boolean isTrackpadScroll(boolean isTrackpadGestureFeaturesEnabled,
- MotionEvent event) {
- return isTrackpadGestureFeaturesEnabled
- && event.getClassification() == CLASSIFICATION_TWO_FINGER_SWIPE;
+ public static boolean isTrackpadScroll(MotionEvent event) {
+ return event.getClassification() == CLASSIFICATION_TWO_FINGER_SWIPE;
}
- public static boolean isTrackpadThreeFingerSwipe(boolean isTrackpadGestureFeaturesEnabled,
- MotionEvent event) {
- return isTrackpadGestureFeaturesEnabled
- && event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE
+ public static boolean isTrackpadThreeFingerSwipe(MotionEvent event) {
+ return event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE
&& event.getAxisValue(AXIS_GESTURE_SWIPE_FINGER_COUNT) == 3;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index 9698548dd30d..54a59f30c8fd 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -38,7 +38,7 @@ import java.util.concurrent.Executor
import javax.inject.Inject
/** Class responsible to "glue" all note task dependencies. */
-internal class NoteTaskInitializer
+class NoteTaskInitializer
@Inject
constructor(
private val controller: NoteTaskController,
@@ -138,11 +138,12 @@ constructor(
* Returns a [NoteTaskEntryPoint] if an action should be taken, and null otherwise.
*/
private fun KeyEvent.toNoteTaskEntryPointOrNull(): NoteTaskEntryPoint? {
- val entryPoint = when {
- keyCode == KEYCODE_STYLUS_BUTTON_TAIL && isTailButtonNotesGesture() -> TAIL_BUTTON
- keyCode == KEYCODE_N && isMetaPressed && isCtrlPressed -> KEYBOARD_SHORTCUT
- else -> null
- }
+ val entryPoint =
+ when {
+ keyCode == KEYCODE_STYLUS_BUTTON_TAIL && isTailButtonNotesGesture() -> TAIL_BUTTON
+ keyCode == KEYCODE_N && isMetaPressed && isCtrlPressed -> KEYBOARD_SHORTCUT
+ else -> null
+ }
debugLog { "toNoteTaskEntryPointOrNull: entryPoint=$entryPoint" }
return entryPoint
}
@@ -164,7 +165,9 @@ constructor(
// For now, trigger action immediately on UP of a single press, without waiting for
// the multi-press timeout to expire.
- debugLog { "isTailButtonNotesGesture: isMultiPress=$isMultiPress, isLongPress=$isLongPress" }
+ debugLog {
+ "isTailButtonNotesGesture: isMultiPress=$isMultiPress, isLongPress=$isLongPress"
+ }
return !isMultiPress && !isLongPress
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index ca71870845e0..40cf4a4fd403 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -185,8 +185,9 @@ open class QSTileViewImpl @JvmOverloads constructor(
private val colorEvaluator = ArgbEvaluator.getInstance()
val isLongPressEffectInitialized: Boolean
get() = longPressEffect?.hasInitialized == true
- @VisibleForTesting
- var longPressEffectHandle: DisposableHandle? = null
+ private var longPressEffectHandle: DisposableHandle? = null
+ val isLongPressEffectBound: Boolean
+ get() = longPressEffectHandle != null
init {
val typedValue = TypedValue()
@@ -621,11 +622,14 @@ open class QSTileViewImpl @JvmOverloads constructor(
// Long-press effects
if (state.handlesLongClick &&
longPressEffect?.initializeEffect(longPressEffectDuration) == true) {
- // set the valid long-press effect as the touch listener
- if (longPressEffectHandle == null) {
+ // bind the long-press effect and set it as the touch listener
+ if (!isLongPressEffectBound) {
longPressEffectHandle =
- QSLongPressEffectViewBinder.bind(this, longPressEffect, state.spec)
- setOnTouchListener(longPressEffect)
+ QSLongPressEffectViewBinder.bind(
+ this,
+ longPressEffect,
+ state.spec,
+ )
}
showRippleEffect = false
initializeLongPressProperties()
@@ -634,8 +638,7 @@ open class QSTileViewImpl @JvmOverloads constructor(
// handle a long-press. In this case, we go back to the behaviour of a regular tile
// and clean-up the resources
setOnTouchListener(null)
- longPressEffectHandle?.dispose()
- longPressEffectHandle = null
+ unbindLongPressEffect()
showRippleEffect = isClickable
initialLongPressProperties = null
finalLongPressProperties = null
@@ -827,6 +830,11 @@ open class QSTileViewImpl @JvmOverloads constructor(
changeCornerRadius(newRadius)
}
+ private fun unbindLongPressEffect() {
+ longPressEffectHandle?.dispose()
+ longPressEffectHandle = null
+ }
+
private fun interpolateFloat(fraction: Float, start: Float, end: Float): Float =
start + fraction * (end - start)
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index 4d34a869002a..4e290e699ac4 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -47,6 +47,7 @@ import java.util.concurrent.Executor
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import javax.inject.Inject
+import kotlin.jvm.optionals.getOrElse
class IssueRecordingService
@Inject
@@ -140,15 +141,25 @@ constructor(
}
private fun shareRecording(screenRecording: Uri?) {
- val sharableUri: Uri =
- zipAndPackageRecordings(
- TraceUtils.traceDump(contentResolver, TRACE_FILE_NAME).get(),
- screenRecording
- )
- ?: return
+ val traces =
+ TraceUtils.traceDump(contentResolver, TRACE_FILE_NAME).getOrElse {
+ Log.v(
+ TAG,
+ "Traces were not present. This can happen if users double" +
+ "click on share notification. Traces are cleaned up after sharing" +
+ "so they won't be present for the 2nd share attempt."
+ )
+ return
+ }
+ val perfetto = FileProvider.getUriForFile(this, AUTHORITY, traces.first())
+ val urisToShare = mutableListOf(perfetto)
+ traces.removeFirst()
+
+ getZipWinscopeFileUri(traces)?.let { urisToShare.add(it) }
+ screenRecording?.let { urisToShare.add(it) }
+
val sendIntent =
- FileSender.buildSendIntent(this, listOf(sharableUri))
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ FileSender.buildSendIntent(this, urisToShare).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
// TODO: Debug why the notification shade isn't closing upon starting the BetterBug activity
mKeyguardDismissUtil.executeWhenUnlocked(
@@ -161,7 +172,7 @@ constructor(
)
}
- private fun zipAndPackageRecordings(traceFiles: List<File>, screenRecording: Uri?): Uri? {
+ private fun getZipWinscopeFileUri(traceFiles: List<File>): Uri? {
try {
externalCacheDir?.mkdirs()
val outZip: File = File.createTempFile(TEMP_FILE_PREFIX, ZIP_SUFFIX, externalCacheDir)
@@ -171,13 +182,6 @@ constructor(
Files.copy(file.toPath(), os)
os.closeEntry()
}
- if (screenRecording != null) {
- contentResolver.openInputStream(screenRecording)?.use {
- os.putNextEntry(ZipEntry(SCREEN_RECORDING_ZIP_LABEL))
- it.transferTo(os)
- os.closeEntry()
- }
- }
}
return FileProvider.getUriForFile(this, AUTHORITY, outZip)
} catch (e: Exception) {
@@ -192,8 +196,7 @@ constructor(
private const val EXTRA_SCREEN_RECORD = "extra_screenRecord"
private const val EXTRA_WINSCOPE_TRACING = "extra_winscopeTracing"
private const val ZIP_SUFFIX = ".zip"
- private const val TEMP_FILE_PREFIX = "issue_recording"
- private const val SCREEN_RECORDING_ZIP_LABEL = "screen-recording.mp4"
+ private const val TEMP_FILE_PREFIX = "winscope_recordings"
private val DEFAULT_TRACE_TAGS = listOf<String>()
private const val DEFAULT_BUFFER_SIZE = 16384
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index ec7707c83980..e56a4f45d7aa 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -22,7 +22,7 @@ import kotlinx.coroutines.launch
interface TakeScreenshotExecutor {
suspend fun executeScreenshots(
screenshotRequest: ScreenshotRequest,
- onSaved: (Uri) -> Unit,
+ onSaved: (Uri?) -> Unit,
requestCallback: RequestCallback
)
fun onCloseSystemDialogsReceived()
@@ -30,7 +30,7 @@ interface TakeScreenshotExecutor {
fun onDestroy()
fun executeScreenshotsAsync(
screenshotRequest: ScreenshotRequest,
- onSaved: Consumer<Uri>,
+ onSaved: Consumer<Uri?>,
requestCallback: RequestCallback
)
}
@@ -65,7 +65,7 @@ constructor(
*/
override suspend fun executeScreenshots(
screenshotRequest: ScreenshotRequest,
- onSaved: (Uri) -> Unit,
+ onSaved: (Uri?) -> Unit,
requestCallback: RequestCallback
) {
val displayIds = getDisplaysToScreenshot(screenshotRequest.type)
@@ -86,7 +86,7 @@ constructor(
/** All logging should be triggered only by this method. */
private suspend fun dispatchToController(
rawScreenshotData: ScreenshotData,
- onSaved: (Uri) -> Unit,
+ onSaved: (Uri?) -> Unit,
callback: RequestCallback
) {
// Let's wait before logging "screenshot requested", as we should log the processed
@@ -185,7 +185,7 @@ constructor(
/** For java compatibility only. see [executeScreenshots] */
override fun executeScreenshotsAsync(
screenshotRequest: ScreenshotRequest,
- onSaved: Consumer<Uri>,
+ onSaved: Consumer<Uri?>,
requestCallback: RequestCallback
) {
mainScope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
index d62ab8574799..1945c2575655 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
@@ -39,11 +39,11 @@ constructor(
override suspend fun check(content: DisplayContentModel): PolicyResult {
// The systemUI notification shade isn't a private profile app, skip.
if (content.systemUiState.shadeExpanded) {
- return NotMatched(policy = NAME, reason = "Notification shade is expanded")
+ return NotMatched(policy = NAME, reason = SHADE_EXPANDED)
}
// Find the first visible rootTaskInfo with a child task owned by a private user
- val (rootTask, childTask) =
+ val childTask =
content.rootTasks
.filter { it.isVisible }
.firstNotNullOfOrNull { root ->
@@ -52,22 +52,24 @@ constructor(
.firstOrNull {
profileTypes.getProfileType(it.userId) == ProfileType.PRIVATE
}
- ?.let { root to it }
}
- ?: return NotMatched(policy = NAME, reason = "No private profile tasks are visible")
+ ?: return NotMatched(policy = NAME, reason = NO_VISIBLE_TASKS)
// If matched, return parameters needed to modify the request.
return Matched(
policy = NAME,
- reason = "At least one private profile task is visible",
+ reason = PRIVATE_TASK_VISIBLE,
CaptureParameters(
type = FullScreen(content.displayId),
- component = childTask.componentName ?: rootTask.topActivity,
+ component = content.rootTasks.first { it.isVisible }.topActivity,
owner = UserHandle.of(childTask.userId),
)
)
}
companion object {
const val NAME = "PrivateProfile"
+ const val SHADE_EXPANDED = "Notification shade is expanded"
+ const val NO_VISIBLE_TASKS = "No private profile tasks are visible"
+ const val PRIVATE_TASK_VISIBLE = "At least one private profile task is visible"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
index 3789371d7c33..f768cfb2ceb5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
@@ -30,3 +30,5 @@ internal fun RootTaskInfo.childTasksTopDown(): Sequence<ChildTaskModel> {
)
}
}
+
+internal fun RootTaskInfo.hasChildTasks() = childTaskUserIds.isNotEmpty()
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
index b781ae99a4de..fdf16aa9d081 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
@@ -16,6 +16,7 @@
package com.android.systemui.screenshot.policy
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
import android.os.UserHandle
import com.android.systemui.screenshot.data.model.DisplayContentModel
@@ -24,6 +25,7 @@ import com.android.systemui.screenshot.data.repository.ProfileTypeRepository
import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult
import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.NotMatched
import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
+import com.android.window.flags.Flags
import javax.inject.Inject
import kotlinx.coroutines.flow.first
@@ -41,26 +43,36 @@ constructor(
override suspend fun check(content: DisplayContentModel): PolicyResult {
// The systemUI notification shade isn't a work app, skip.
if (content.systemUiState.shadeExpanded) {
- return NotMatched(policy = NAME, reason = "Notification shade is expanded")
+ return NotMatched(policy = NAME, reason = SHADE_EXPANDED)
+ }
+
+ if (Flags.enableDesktopWindowingMode()) {
+ content.rootTasks.firstOrNull()?.also {
+ if (it.windowingMode == WINDOWING_MODE_FREEFORM) {
+ return NotMatched(policy = NAME, reason = DESKTOP_MODE_ENABLED)
+ }
+ }
}
// Find the first non PiP rootTask with a top child task owned by a work user
val (rootTask, childTask) =
content.rootTasks
- .filter { it.isVisible && it.windowingMode != WINDOWING_MODE_PINNED }
+ .filter {
+ it.isVisible && it.windowingMode != WINDOWING_MODE_PINNED && it.hasChildTasks()
+ }
.map { it to it.childTasksTopDown().first() }
.firstOrNull { (_, child) ->
profileTypes.getProfileType(child.userId) == ProfileType.WORK
}
?: return NotMatched(
policy = NAME,
- reason = "The top-most non-PINNED task does not belong to a work profile user"
+ reason = WORK_TASK_NOT_TOP,
)
// If matched, return parameters needed to modify the request.
return PolicyResult.Matched(
policy = NAME,
- reason = "The top-most non-PINNED task ($childTask) belongs to a work profile user",
+ reason = WORK_TASK_IS_TOP,
CaptureParameters(
type = IsolatedTask(taskId = childTask.id, taskBounds = childTask.bounds),
component = childTask.componentName ?: rootTask.topActivity,
@@ -70,6 +82,13 @@ constructor(
}
companion object {
- val NAME = "WorkProfile"
+ const val NAME = "WorkProfile"
+ const val SHADE_EXPANDED = "Notification shade is expanded"
+ const val WORK_TASK_NOT_TOP =
+ "The top-most non-PINNED task does not belong to a work profile user"
+ const val WORK_TASK_IS_TOP = "The top-most non-PINNED task belongs to a work profile user"
+ const val DESKTOP_MODE_ENABLED =
+ "enable_desktop_windowing_mode is enabled and top " +
+ "RootTask has WINDOWING_MODE_FREEFORM"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index f418e7e0278f..8f6b9d0d19e9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -36,6 +36,7 @@ import com.android.systemui.communal.dagger.Communal
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.ui.compose.CommunalContainer
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.communal.util.CommunalColors
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -64,6 +65,7 @@ constructor(
private val keyguardInteractor: KeyguardInteractor,
private val shadeInteractor: ShadeInteractor,
private val powerManager: PowerManager,
+ private val communalColors: CommunalColors,
@Communal private val dataSourceDelegator: SceneDataSourceDelegator,
) {
/** The container view for the hub. This will not be initialized until [initView] is called. */
@@ -168,6 +170,7 @@ constructor(
PlatformTheme {
CommunalContainer(
viewModel = communalViewModel,
+ colors = communalColors,
dataSourceDelegator = dataSourceDelegator,
dialogFactory = dialogFactory,
)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index b8512f2de0a6..aa915e3721ae 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -191,6 +191,7 @@ import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor;
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -291,7 +292,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
*/
public final boolean mAnimateBack;
- private final boolean mTrackpadGestureFeaturesEnabled;
/**
* The minimum scale to "squish" the Shade and associated elements down to, for Back gesture
*/
@@ -438,6 +438,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private boolean mExpandingFromHeadsUp;
private boolean mCollapsedOnDown;
private boolean mClosingWithAlphaFadeOut;
+ private boolean mHeadsUpVisible;
private boolean mHeadsUpAnimatingAway;
private final FalsingManager mFalsingManager;
private final FalsingCollector mFalsingCollector;
@@ -605,6 +606,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private final PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
private final SharedNotificationContainerInteractor mSharedNotificationContainerInteractor;
private final ActiveNotificationsInteractor mActiveNotificationsInteractor;
+ private final HeadsUpNotificationInteractor mHeadsUpNotificationInteractor;
private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
private final KeyguardInteractor mKeyguardInteractor;
private final PowerInteractor mPowerInteractor;
@@ -770,6 +772,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
ActivityStarter activityStarter,
SharedNotificationContainerInteractor sharedNotificationContainerInteractor,
ActiveNotificationsInteractor activeNotificationsInteractor,
+ HeadsUpNotificationInteractor headsUpNotificationInteractor,
ShadeAnimationInteractor shadeAnimationInteractor,
KeyguardViewConfigurator keyguardViewConfigurator,
DeviceEntryFaceAuthInteractor deviceEntryFaceAuthInteractor,
@@ -804,6 +807,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
mSharedNotificationContainerInteractor = sharedNotificationContainerInteractor;
mActiveNotificationsInteractor = activeNotificationsInteractor;
+ mHeadsUpNotificationInteractor = headsUpNotificationInteractor;
mKeyguardInteractor = keyguardInteractor;
mPowerInteractor = powerInteractor;
mKeyguardViewConfigurator = keyguardViewConfigurator;
@@ -886,7 +890,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mLayoutInflater = layoutInflater;
mFeatureFlags = featureFlags;
mAnimateBack = predictiveBackAnimateShade();
- mTrackpadGestureFeaturesEnabled = mFeatureFlags.isEnabled(Flags.TRACKPAD_GESTURE_FEATURES);
mFalsingCollector = falsingCollector;
mWakeUpCoordinator = coordinator;
mMainDispatcher = mainDispatcher;
@@ -1216,6 +1219,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
},
mMainDispatcher);
+
+ if (NotificationsHeadsUpRefactor.isEnabled()) {
+ collectFlow(mView, mHeadsUpNotificationInteractor.isHeadsUpOrAnimatingAway(),
+ setHeadsUpVisible(), mMainDispatcher);
+ }
}
@VisibleForTesting
@@ -3055,7 +3063,21 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mPanelAlphaEndAction = r;
}
+ private Consumer<Boolean> setHeadsUpVisible() {
+ return (Boolean isHeadsUpVisible) -> {
+ mHeadsUpVisible = isHeadsUpVisible;
+
+ if (isHeadsUpVisible) {
+ updateNotificationTranslucency();
+ }
+ updateExpansionAndVisibility();
+ updateGestureExclusionRect();
+ mKeyguardStatusBarViewController.updateForHeadsUp();
+ };
+ }
+
private void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
+ NotificationsHeadsUpRefactor.assertInLegacyMode();
mHeadsUpAnimatingAway = headsUpAnimatingAway;
mNotificationStackScrollLayoutController.setHeadsUpAnimatingAway(headsUpAnimatingAway);
updateVisibility();
@@ -3071,13 +3093,16 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
private boolean shouldPanelBeVisible() {
- boolean headsUpVisible = mHeadsUpAnimatingAway || mHeadsUpPinnedMode;
+ boolean headsUpVisible = NotificationsHeadsUpRefactor.isEnabled() ? mHeadsUpVisible
+ : (mHeadsUpAnimatingAway || mHeadsUpPinnedMode);
return headsUpVisible || isExpanded() || mBouncerShowing;
}
private void setHeadsUpManager(HeadsUpManager headsUpManager) {
mHeadsUpManager = headsUpManager;
- mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
+ if (!NotificationsHeadsUpRefactor.isEnabled()) {
+ mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
+ }
mHeadsUpTouchHelper = new HeadsUpTouchHelper(
headsUpManager,
mStatusBarService,
@@ -3165,8 +3190,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
private boolean isPanelVisibleBecauseOfHeadsUp() {
- return (mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway)
- && mBarState == StatusBarState.SHADE;
+ boolean headsUpVisible = NotificationsHeadsUpRefactor.isEnabled() ? mHeadsUpVisible
+ : (mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway);
+ return headsUpVisible && mBarState == StatusBarState.SHADE;
}
private boolean isPanelVisibleBecauseScrimIsAnimatingOff() {
@@ -3479,6 +3505,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
ipw.print("mExpandingFromHeadsUp="); ipw.println(mExpandingFromHeadsUp);
ipw.print("mCollapsedOnDown="); ipw.println(mCollapsedOnDown);
ipw.print("mClosingWithAlphaFadeOut="); ipw.println(mClosingWithAlphaFadeOut);
+ ipw.print("mHeadsUpVisible="); ipw.println(mHeadsUpVisible);
ipw.print("mHeadsUpAnimatingAway="); ipw.println(mHeadsUpAnimatingAway);
ipw.print("mShowIconsWhenExpanded="); ipw.println(mShowIconsWhenExpanded);
ipw.print("mIndicationBottomPadding="); ipw.println(mIndicationBottomPadding);
@@ -4384,6 +4411,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private final class ShadeHeadsUpChangedListener implements OnHeadsUpChangedListener {
@Override
public void onHeadsUpPinnedModeChanged(final boolean inPinnedMode) {
+ NotificationsHeadsUpRefactor.assertInLegacyMode();
+
if (inPinnedMode) {
mHeadsUpExistenceChangedRunnable.run();
updateNotificationTranslucency();
@@ -4400,9 +4429,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
@Override
public void onHeadsUpPinned(NotificationEntry entry) {
- if (NotificationsHeadsUpRefactor.isEnabled()) {
- return;
- }
+ NotificationsHeadsUpRefactor.assertInLegacyMode();
if (!isKeyguardShowing()) {
mNotificationStackScrollLayoutController.generateHeadsUpAnimation(entry, true);
@@ -4411,9 +4438,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
@Override
public void onHeadsUpUnPinned(NotificationEntry entry) {
- if (NotificationsHeadsUpRefactor.isEnabled()) {
- return;
- }
+ NotificationsHeadsUpRefactor.assertInLegacyMode();
// When we're unpinning the notification via active edge they remain heads-upped,
// we need to make sure that an animation happens in this case, otherwise the
@@ -4898,9 +4923,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
final float x = event.getX(pointerIndex);
final float y = event.getY(pointerIndex);
boolean canCollapsePanel = canCollapsePanelOnTouch();
- final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll(
- mTrackpadGestureFeaturesEnabled, event) || isTrackpadThreeFingerSwipe(
- mTrackpadGestureFeaturesEnabled, event);
+ final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll(event)
+ || isTrackpadThreeFingerSwipe(event);
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
@@ -4920,7 +4944,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mIsTrackpadReverseScroll =
!mNaturalScrollingSettingObserver.isNaturalScrollingEnabled()
- && isTrackpadScroll(mTrackpadGestureFeaturesEnabled, event);
+ && isTrackpadScroll(event);
if (!isTracking() || isFullyCollapsed()) {
mInitialExpandY = y;
mInitialExpandX = x;
@@ -5143,9 +5167,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mIgnoreXTouchSlop = true;
}
- final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll(
- mTrackpadGestureFeaturesEnabled, event) || isTrackpadThreeFingerSwipe(
- mTrackpadGestureFeaturesEnabled, event);
+ final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll(event)
+ || isTrackpadThreeFingerSwipe(event);
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index a763641841d9..907cf5eb6886 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -17,7 +17,6 @@
package com.android.systemui.shade;
import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
-import static com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON;
import static com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING;
import static com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
@@ -104,7 +103,6 @@ public class NotificationShadeWindowViewController implements Dumpable {
private final PulsingGestureListener mPulsingGestureListener;
private final LockscreenHostedDreamGestureListener mLockscreenHostedDreamGestureListener;
private final NotificationInsetsController mNotificationInsetsController;
- private final boolean mIsTrackpadCommonEnabled;
private final FeatureFlagsClassic mFeatureFlagsClassic;
private final SysUIKeyEventHandler mSysUIKeyEventHandler;
private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
@@ -211,7 +209,6 @@ public class NotificationShadeWindowViewController implements Dumpable {
mLockscreenHostedDreamGestureListener = lockscreenHostedDreamGestureListener;
mNotificationInsetsController = notificationInsetsController;
mGlanceableHubContainerController = glanceableHubContainerController;
- mIsTrackpadCommonEnabled = featureFlagsClassic.isEnabled(TRACKPAD_GESTURE_COMMON);
mFeatureFlagsClassic = featureFlagsClassic;
mSysUIKeyEventHandler = sysUIKeyEventHandler;
mPrimaryBouncerInteractor = primaryBouncerInteractor;
@@ -643,16 +640,10 @@ public class NotificationShadeWindowViewController implements Dumpable {
if (mTouchActive) {
final long now = mClock.uptimeMillis();
final MotionEvent event;
- if (mIsTrackpadCommonEnabled) {
- event = MotionEvent.obtain(mDownEvent);
- event.setDownTime(now);
- event.setAction(MotionEvent.ACTION_CANCEL);
- event.setLocation(0.0f, 0.0f);
- } else {
- event = MotionEvent.obtain(now, now,
- MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
- event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
- }
+ event = MotionEvent.obtain(mDownEvent);
+ event.setDownTime(now);
+ event.setAction(MotionEvent.ACTION_CANCEL);
+ event.setLocation(0.0f, 0.0f);
Log.w(TAG, "Canceling current touch event (should be very rare)");
mView.dispatchTouchEvent(event);
event.recycle();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index e3db62634339..222b070d151d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -33,9 +33,9 @@ import com.android.systemui.plugins.qs.QS
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.res.R
-import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
@@ -813,7 +813,7 @@ class DragDownHelper(
initialTouchX = x
isTrackpadReverseScroll =
!naturalScrollingSettingObserver.isNaturalScrollingEnabled &&
- isTrackpadScroll(true, event)
+ isTrackpadScroll(event)
}
MotionEvent.ACTION_MOVE -> {
val h = (if (isTrackpadReverseScroll) -1 else 1) * (y - initialTouchY)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt
index 77660eb7d864..e9306a5bd209 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.data.repository
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
/**
* A repository of currently displayed heads up notifications.
@@ -31,11 +32,13 @@ interface HeadsUpRepository {
* True if we are exiting the headsUp pinned mode, and some notifications might still be
* animating out. This is used to keep their view container visible.
*/
- val isHeadsUpAnimatingAway: Flow<Boolean>
+ val isHeadsUpAnimatingAway: StateFlow<Boolean>
/** The heads up row that should be displayed on top. */
val topHeadsUpRow: Flow<HeadsUpRowRepository?>
/** Set of currently active top-level heads up rows to be displayed. */
val activeHeadsUpRows: Flow<Set<HeadsUpRowRepository>>
+
+ fun setHeadsUpAnimatingAway(animatingAway: Boolean)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
index 7f94da3c8c6a..98b52edcf9cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
@@ -29,7 +29,7 @@ import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
-class HeadsUpNotificationInteractor @Inject constructor(repository: HeadsUpRepository) {
+class HeadsUpNotificationInteractor @Inject constructor(private val repository: HeadsUpRepository) {
val topHeadsUpRow: Flow<HeadsUpRowKey?> = repository.topHeadsUpRow
@@ -67,6 +67,9 @@ class HeadsUpNotificationInteractor @Inject constructor(repository: HeadsUpRepos
fun headsUpRow(key: HeadsUpRowKey): HeadsUpRowInteractor =
HeadsUpRowInteractor(key as HeadsUpRowRepository)
fun elementKeyFor(key: HeadsUpRowKey) = (key as HeadsUpRowRepository).elementKey
+ fun setHeadsUpAnimatingAway(animatingAway: Boolean) {
+ repository.setHeadsUpAnimatingAway(animatingAway)
+ }
}
class HeadsUpRowInteractor(repository: HeadsUpRowRepository)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 0c8518f0b284..bdeaabf53b07 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -2069,6 +2069,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
// Remove views that don't translate
mTranslateableViews.remove(mChildrenContainerStub);
mTranslateableViews.remove(mGutsStub);
+ // We don't handle focus highlight in this view, it's done in background drawable instead
+ setDefaultFocusHighlightEnabled(false);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 82559dec9f86..8a1a4f1e4cd8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -111,6 +111,7 @@ import com.android.systemui.statusbar.notification.row.ActivatableNotificationVi
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation;
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds;
@@ -450,7 +451,9 @@ public class NotificationStackScrollLayout
private boolean mIsClipped;
private Rect mRequestedClipBounds;
private boolean mInHeadsUpPinnedMode;
- private boolean mHeadsUpAnimatingAway;
+ @VisibleForTesting
+ boolean mHeadsUpAnimatingAway;
+ private Consumer<Boolean> mHeadsUpAnimatingAwayListener;
private int mStatusBarState;
private int mUpcomingStatusBarState;
private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
@@ -4084,7 +4087,14 @@ public class NotificationStackScrollLayout
mSwipeHelper.setIsExpanded(isExpanded);
if (changed) {
mWillExpand = false;
- if (!mIsExpanded) {
+ if (mIsExpanded) {
+ // Resetting headsUpAnimatingAway on Shade expansion avoids delays caused by
+ // waiting for all child animations to finish.
+ // TODO(b/328390331) Do we need to reset this on QS expanded as well?
+ if (NotificationsHeadsUpRefactor.isEnabled()) {
+ setHeadsUpAnimatingAway(false);
+ }
+ } else {
mGroupExpansionManager.collapseGroups();
mExpandHelper.cancelImmediately();
if (!mIsExpansionChanging) {
@@ -4190,6 +4200,9 @@ public class NotificationStackScrollLayout
void onChildAnimationFinished() {
setAnimationRunning(false);
+ if (NotificationsHeadsUpRefactor.isEnabled()) {
+ setHeadsUpAnimatingAway(false);
+ }
requestChildrenUpdate();
runAnimationFinishedRunnables();
clearTransient();
@@ -4509,18 +4522,18 @@ public class NotificationStackScrollLayout
mEmptyShadeView.setVisible(visible, mIsExpanded && mAnimationsEnabled);
if (areNotificationsHiddenInShade) {
- updateEmptyShadeView(R.string.dnd_suppressing_shade_text, 0, 0);
+ updateEmptyShadeViewResources(R.string.dnd_suppressing_shade_text, 0, 0);
} else if (hasFilteredOutSeenNotifications) {
- updateEmptyShadeView(
+ updateEmptyShadeViewResources(
R.string.no_unseen_notif_text,
R.string.unlock_to_see_notif_text,
R.drawable.ic_friction_lock_closed);
} else {
- updateEmptyShadeView(R.string.empty_shade_text, 0, 0);
+ updateEmptyShadeViewResources(R.string.empty_shade_text, 0, 0);
}
}
- private void updateEmptyShadeView(
+ private void updateEmptyShadeViewResources(
@StringRes int newTextRes,
@StringRes int newFooterTextRes,
@DrawableRes int newFooterIconRes) {
@@ -4717,6 +4730,7 @@ public class NotificationStackScrollLayout
}
public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) {
+ NotificationsHeadsUpRefactor.assertInLegacyMode();
ExpandableNotificationRow row = entry.getHeadsUpAnimationView();
generateHeadsUpAnimation(row, isHeadsUp);
}
@@ -4750,6 +4764,9 @@ public class NotificationStackScrollLayout
mNeedsAnimation = true;
if (!mIsExpanded && !mWillExpand && !isHeadsUp) {
row.setHeadsUpAnimatingAway(true);
+ if (NotificationsHeadsUpRefactor.isEnabled()) {
+ setHeadsUpAnimatingAway(true);
+ }
}
requestChildrenUpdate();
}
@@ -4939,11 +4956,28 @@ public class NotificationStackScrollLayout
updateClipping();
}
+ /** TODO(b/328390331) make this private, when {@link NotificationsHeadsUpRefactor} is removed */
public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
- mHeadsUpAnimatingAway = headsUpAnimatingAway;
+ if (mHeadsUpAnimatingAway != headsUpAnimatingAway) {
+ mHeadsUpAnimatingAway = headsUpAnimatingAway;
+ if (mHeadsUpAnimatingAwayListener != null) {
+ mHeadsUpAnimatingAwayListener.accept(headsUpAnimatingAway);
+ }
+ }
updateClipping();
}
+ /**
+ * Sets a listener to be notified about the heads up disappear animation state changes. If there
+ * are overlapping animations, it will receive updates when the first disappar animation has
+ * started, and when the last has finished.
+ *
+ * @param headsUpAnimatingAwayListener to be notified about disappear animation state changes.
+ */
+ public void setHeadsUpAnimatingAwayListener(
+ Consumer<Boolean> headsUpAnimatingAwayListener) {
+ mHeadsUpAnimatingAwayListener = headsUpAnimatingAwayListener;
+ }
@VisibleForTesting
public void setStatusBarState(int statusBarState) {
mStatusBarState = statusBarState;
@@ -5338,7 +5372,8 @@ public class NotificationStackScrollLayout
mActivityStarter.startActivity(intent, true, true, Intent.FLAG_ACTIVITY_SINGLE_TOP);
});
setEmptyShadeView(view);
- updateEmptyShadeView(
+ view.setVisible(oldView != null && oldView.isVisible(), /* animate = */ false);
+ updateEmptyShadeViewResources(
oldView == null ? R.string.empty_shade_text : oldView.getTextResource(),
oldView == null ? 0 : oldView.getFooterTextResource(),
oldView == null ? 0 : oldView.getFooterIconResource());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 06479e5a8e0e..5e719b1f0667 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -68,8 +68,6 @@ import com.android.systemui.classifier.Classifier;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlagsClassic;
-import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.MigrateClocksToBlueprint;
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
import com.android.systemui.keyguard.shared.model.KeyguardState;
@@ -210,11 +208,9 @@ public class NotificationStackScrollLayoutController implements Dumpable {
@Nullable
private Boolean mHistoryEnabled;
private int mBarState;
- private boolean mIsBouncerShowingFromCentralSurfaces;
private HeadsUpAppearanceController mHeadsUpAppearanceController;
private boolean mIsInTransitionToAod = false;
- private final FeatureFlagsClassic mFeatureFlags;
private final NotificationTargetsHelper mNotificationTargetsHelper;
private final SecureSettings mSecureSettings;
private final NotificationDismissibilityProvider mDismissibilityProvider;
@@ -745,7 +741,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
StackStateLogger stackLogger,
NotificationStackScrollLogger logger,
NotificationStackSizeCalculator notificationStackSizeCalculator,
- FeatureFlagsClassic featureFlags,
NotificationTargetsHelper notificationTargetsHelper,
SecureSettings secureSettings,
NotificationDismissibilityProvider dismissibilityProvider,
@@ -793,7 +788,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
mSeenNotificationsInteractor = seenNotificationsInteractor;
mShadeController = shadeController;
mWindowRootView = windowRootView;
- mFeatureFlags = featureFlags;
mNotificationTargetsHelper = notificationTargetsHelper;
mSecureSettings = secureSettings;
mDismissibilityProvider = dismissibilityProvider;
@@ -1391,14 +1385,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
/**
- * Sets whether the bouncer is currently showing. Should only be called from
- * {@link CentralSurfaces}.
- */
- public void setBouncerShowingFromCentralSurfaces(boolean bouncerShowing) {
- mIsBouncerShowingFromCentralSurfaces = bouncerShowing;
- }
-
- /**
* Set the visibility of the view, and propagate it to specific children.
*
* @param visible either the view is visible or not.
@@ -1435,7 +1421,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
// For more details, see: b/228790482
&& !mIsInTransitionToAod
// Don't show any notification content if the bouncer is showing. See b/267060171.
- && !isBouncerShowing();
+ && !mPrimaryBouncerInteractor.isBouncerShowing();
mView.updateEmptyShadeView(shouldShow, mZenModeController.areNotificationsHiddenInShade());
@@ -1443,24 +1429,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
/**
- * Returns whether the bouncer is currently showing.
- *
- * There's a possible timing difference between when CentralSurfaces marks the bouncer as not
- * showing and when PrimaryBouncerInteractor marks the bouncer as not showing. (CentralSurfaces
- * appears to mark the bouncer as showing for 10-200ms longer than PrimaryBouncerInteractor.)
- *
- * This timing difference could be load bearing, which is why we have a feature flag protecting
- * where we fetch the value from. This flag is intended to be short-lived.
- */
- private boolean isBouncerShowing() {
- if (mFeatureFlags.isEnabled(Flags.USE_REPOS_FOR_BOUNCER_SHOWING)) {
- return mPrimaryBouncerInteractor.isBouncerShowing();
- } else {
- return mIsBouncerShowingFromCentralSurfaces;
- }
- }
-
- /**
* Update the importantForAccessibility of NotificationStackScrollLayout.
* <p>
* We want the NSSL to be unimportant for accessibility when there's no
@@ -1482,6 +1450,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
+ NotificationsHeadsUpRefactor.assertInLegacyMode();
mView.setHeadsUpAnimatingAway(headsUpAnimatingAway);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index 5ab58576a89d..3a89630ebe77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
@@ -31,6 +32,7 @@ import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.Notificati
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackInteractor
import com.android.systemui.statusbar.policy.domain.interactor.UserSetupInteractor
import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
+import com.android.systemui.util.kotlin.FlowDumperImpl
import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.ui.AnimatableEvent
import com.android.systemui.util.ui.AnimatedValue
@@ -64,7 +66,8 @@ constructor(
userSetupInteractor: UserSetupInteractor,
zenModeInteractor: ZenModeInteractor,
@Background bgDispatcher: CoroutineDispatcher,
-) {
+ dumpManager: DumpManager,
+) : FlowDumperImpl(dumpManager) {
/**
* We want the NSSL to be unimportant for accessibility when there are no notifications in it
* while the device is on lock screen, to avoid an unlabelled NSSL view in TalkBack. Otherwise,
@@ -81,8 +84,9 @@ constructor(
) { hasNotifications, isShowingOnLockscreen ->
hasNotifications || !isShowingOnLockscreen
}
- .flowOn(bgDispatcher)
.distinctUntilChanged()
+ .dumpWhileCollecting("isImportantForAccessibility")
+ .flowOn(bgDispatcher)
}
}
@@ -105,8 +109,9 @@ constructor(
else -> true
}
}
- .flowOn(bgDispatcher)
.distinctUntilChanged()
+ .dumpWhileCollecting("shouldShowEmptyShadeView")
+ .flowOn(bgDispatcher)
}
}
@@ -125,8 +130,9 @@ constructor(
// the footer to be counted as part of the shade for measurements.
shadeInteractor.shadeExpansion
.map { it == 0f }
- .flowOn(bgDispatcher)
.distinctUntilChanged()
+ .dumpWhileCollecting("shouldHideFooterView")
+ .flowOn(bgDispatcher)
}
}
@@ -173,7 +179,6 @@ constructor(
else -> VisibilityChange.APPEAR_WITH_ANIMATION
}
}
- .flowOn(bgDispatcher)
.distinctUntilChanged(
// Equivalent unless visibility changes
areEquivalent = { a: VisibilityChange, b: VisibilityChange ->
@@ -199,6 +204,8 @@ constructor(
AnimatableEvent(visibilityChange.visible, shouldAnimate)
}
.toAnimatedValueFlow()
+ .dumpWhileCollecting("shouldIncludeFooterView")
+ .flowOn(bgDispatcher)
}
}
@@ -213,7 +220,9 @@ constructor(
if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
flowOf(false)
} else {
- zenModeInteractor.areNotificationsHiddenInShade
+ zenModeInteractor.areNotificationsHiddenInShade.dumpWhileCollecting(
+ "areNotificationsHiddenInShade"
+ )
}
}
@@ -222,7 +231,9 @@ constructor(
if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
flowOf(false)
} else {
- seenNotificationsInteractor.hasFilteredOutSeenNotifications
+ seenNotificationsInteractor.hasFilteredOutSeenNotifications.dumpWhileCollecting(
+ "hasFilteredOutSeenNotifications"
+ )
}
}
@@ -230,7 +241,9 @@ constructor(
if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
flowOf(false)
} else {
- activeNotificationsInteractor.hasClearableAlertingNotifications
+ activeNotificationsInteractor.hasClearableAlertingNotifications.dumpWhileCollecting(
+ "hasClearableAlertingNotifications"
+ )
}
}
@@ -238,7 +251,9 @@ constructor(
if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
flowOf(false)
} else {
- activeNotificationsInteractor.hasNonClearableSilentNotifications
+ activeNotificationsInteractor.hasNonClearableSilentNotifications.dumpWhileCollecting(
+ "hasNonClearableSilentNotifications"
+ )
}
}
@@ -246,7 +261,7 @@ constructor(
if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
flowOf(null)
} else {
- headsUpNotificationInteractor.topHeadsUpRow
+ headsUpNotificationInteractor.topHeadsUpRow.dumpWhileCollecting("topHeadsUpRow")
}
}
@@ -254,15 +269,20 @@ constructor(
if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
flowOf(emptySet())
} else {
- headsUpNotificationInteractor.pinnedHeadsUpRows
+ headsUpNotificationInteractor.pinnedHeadsUpRows.dumpWhileCollecting("pinnedHeadsUpRows")
}
}
val headsUpAnimationsEnabled: Flow<Boolean> by lazy {
- combine(keyguardInteractor.isKeyguardShowing, shadeInteractor.isShadeFullyExpanded) {
- (isKeyguardShowing, isShadeFullyExpanded) ->
- // TODO(b/325936094) use isShadeFullyCollapsed instead
- !isKeyguardShowing && !isShadeFullyExpanded
+ if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
+ flowOf(false)
+ } else {
+ combine(keyguardInteractor.isKeyguardShowing, shadeInteractor.isShadeFullyExpanded) {
+ (isKeyguardShowing, isShadeFullyExpanded) ->
+ // TODO(b/325936094) use isShadeFullyCollapsed instead
+ !isKeyguardShowing && !isShadeFullyExpanded
+ }
+ .dumpWhileCollecting("headsUpAnimationsEnabled")
}
}
@@ -270,7 +290,7 @@ constructor(
if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
flowOf(false)
} else {
- headsUpNotificationInteractor.hasPinnedRows
+ headsUpNotificationInteractor.hasPinnedRows.dumpWhileCollecting("hasPinnedHeadsUpRow")
}
}
@@ -279,4 +299,8 @@ constructor(
HeadsUpRowViewModel(headsUpNotificationInteractor.headsUpRow(key))
fun elementKeyFor(key: HeadsUpRowKey): Any = headsUpNotificationInteractor.elementKeyFor(key)
+
+ fun setHeadsUpAnimatingAway(animatingAway: Boolean) {
+ headsUpNotificationInteractor.setHeadsUpAnimatingAway(animatingAway)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
index cb360fed77bc..6acb12a3e2af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
@@ -17,18 +17,18 @@
package com.android.systemui.statusbar.notification.ui.viewbinder
import android.util.Log
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
-private const val TAG = "HunBinder"
-private val DEBUG = true // Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG)
-
class HeadsUpNotificationViewBinder
@Inject
constructor(private val viewModel: NotificationListViewModel) {
@@ -39,10 +39,6 @@ constructor(private val viewModel: NotificationListViewModel) {
viewModel.pinnedHeadsUpRows
.sample(viewModel.headsUpAnimationsEnabled, ::Pair)
.collect { (newKeys, animationsEnabled) ->
- if (DEBUG) {
- Log.d(TAG, "update:$newKeys")
- }
-
val added = newKeys - previousKeys
val removed = previousKeys - newKeys
previousKeys = newKeys
@@ -70,9 +66,19 @@ constructor(private val viewModel: NotificationListViewModel) {
launch {
viewModel.hasPinnedHeadsUpRow.collect { parentView.setInHeadsUpPinnedMode(it) }
}
+ launch {
+ parentView.isHeadsUpAnimatingAway.collect { viewModel.setHeadsUpAnimatingAway(it) }
+ }
}
private fun obtainView(key: HeadsUpRowKey): ExpandableNotificationRow {
return viewModel.elementKeyFor(key) as ExpandableNotificationRow
}
}
+
+private val NotificationStackScrollLayout.isHeadsUpAnimatingAway: Flow<Boolean>
+ get() =
+ ConflatedCallbackFlow.conflatedCallbackFlow {
+ setHeadsUpAnimatingAwayListener { animatingAway -> trySend(animatingAway) }
+ awaitClose { setHeadsUpAnimatingAwayListener(null) }
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 9268d1658b80..6546db9a2868 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -24,7 +24,6 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.util.concurrency.DelayableExecutor
import dagger.Lazy
@@ -37,16 +36,10 @@ class ActivityStarterImpl
constructor(
private val statusBarStateController: SysuiStatusBarStateController,
@Main private val mainExecutor: DelayableExecutor,
- legacyActivityStarter: Lazy<LegacyActivityStarterInternalImpl>,
- activityStarterInternal: Lazy<ActivityStarterInternalImpl>,
+ legacyActivityStarter: Lazy<LegacyActivityStarterInternalImpl>
) : ActivityStarter {
- private val activityStarterInternal: ActivityStarterInternal =
- if (SceneContainerFlag.isEnabled) {
- activityStarterInternal.get()
- } else {
- legacyActivityStarter.get()
- }
+ private val activityStarterInternal: ActivityStarterInternal = legacyActivityStarter.get()
override fun startPendingIntentDismissingKeyguard(intent: PendingIntent) {
activityStarterInternal.startPendingIntentDismissingKeyguard(intent = intent)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index cb3c03ebae4d..e9aa7aa57b1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -2416,7 +2416,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mBouncerShowing = bouncerShowing;
mKeyguardBypassController.setBouncerShowing(bouncerShowing);
mPulseExpansionHandler.setBouncerShowing(bouncerShowing);
- mStackScrollerController.setBouncerShowingFromCentralSurfaces(bouncerShowing);
setBouncerShowingForStatusBarComponents(bouncerShowing);
mStatusBarHideIconsForBouncerManager.setBouncerShowingAndTriggerUpdate(bouncerShowing);
mCommandQueue.recomputeDisableFlags(mDisplayId, true /* animate */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 0ddf37db6078..8ec8d1c814bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -168,7 +168,10 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements
updateResources();
}
});
- javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(), this::onShadeOrQsExpanded);
+ if (!NotificationsHeadsUpRefactor.isEnabled()) {
+ javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(),
+ this::onShadeOrQsExpanded);
+ }
}
public void setAnimationStateHandler(AnimationStateHandler handler) {
@@ -262,6 +265,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements
}
private void onShadeOrQsExpanded(Boolean isExpanded) {
+ NotificationsHeadsUpRefactor.assertInLegacyMode();
if (isExpanded != mIsExpanded) {
mIsExpanded = isExpanded;
if (isExpanded) {
@@ -500,7 +504,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements
@Override
@NonNull
- public Flow<Boolean> isHeadsUpAnimatingAway() {
+ public StateFlow<Boolean> isHeadsUpAnimatingAway() {
return mHeadsUpAnimatingAway;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 38b37183b97f..33437792c7ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -658,6 +658,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
updateForHeadsUp(true);
}
+ // TODO(b/328579846) bind the StatusBar visibility to heads up events
void updateForHeadsUp(boolean animate) {
boolean showingKeyguardHeadsUp =
isKeyguardShowing() && mShadeViewStateProvider.shouldHeadsUpBeVisible();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
index 87139ac0cada..da5877b6417f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
@@ -24,6 +24,7 @@ import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
@@ -98,15 +99,21 @@ public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener,
// we need to keep the panel open artificially, let's wait until the
//animation
// is finished.
- mHeadsUpManager.setHeadsUpAnimatingAway(true);
+ setHeadsAnimatingAway(true);
mNsslController.runAfterAnimationFinished(() -> {
if (!mHeadsUpManager.hasPinnedHeadsUp()) {
mNotificationShadeWindowController.setHeadsUpShowing(false);
- mHeadsUpManager.setHeadsUpAnimatingAway(false);
+ setHeadsAnimatingAway(false);
}
mNotificationRemoteInputManager.onPanelCollapsed();
});
}
}
}
+
+ private void setHeadsAnimatingAway(boolean headsUpAnimatingAway) {
+ if (!NotificationsHeadsUpRefactor.isEnabled()) {
+ mHeadsUpManager.setHeadsUpAnimatingAway(headsUpAnimatingAway);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 2b90e649a154..6e24412fcff4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -19,8 +19,6 @@ package com.android.systemui.statusbar.pipeline.dagger
import android.net.wifi.WifiManager
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.log.table.TableLogBuffer
@@ -52,12 +50,9 @@ import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStat
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModelImpl
import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositorySwitcher
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryViaTrackerLibDagger
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.DisabledWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryViaTrackerLib
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository
@@ -131,19 +126,15 @@ abstract class StatusBarPipelineModule {
impl: CollapsedStatusBarViewBinderImpl
): CollapsedStatusBarViewBinder
- @Binds
- @IntoMap
- @ClassKey(WifiRepositoryDagger::class)
- abstract fun bindWifiRepositoryDagger(impl: WifiRepositoryDagger): CoreStartable
-
companion object {
+
@Provides
@SysUISingleton
- fun provideWifiRepositoryDagger(
+ fun provideRealWifiRepository(
wifiManager: WifiManager?,
disabledWifiRepository: DisabledWifiRepository,
wifiRepositoryImplFactory: WifiRepositoryImpl.Factory,
- ): WifiRepositoryDagger {
+ ): RealWifiRepository {
// If we have a null [WifiManager], then the wifi repository should be permanently
// disabled.
return if (wifiManager == null) {
@@ -155,36 +146,6 @@ abstract class StatusBarPipelineModule {
@Provides
@SysUISingleton
- fun provideWifiRepositoryViaTrackerLibDagger(
- wifiManager: WifiManager?,
- disabledWifiRepository: DisabledWifiRepository,
- wifiRepositoryFromTrackerLibFactory: WifiRepositoryViaTrackerLib.Factory,
- ): WifiRepositoryViaTrackerLibDagger {
- // If we have a null [WifiManager], then the wifi repository should be permanently
- // disabled.
- return if (wifiManager == null) {
- disabledWifiRepository
- } else {
- wifiRepositoryFromTrackerLibFactory.create(wifiManager)
- }
- }
-
- @Provides
- @SysUISingleton
- fun provideRealWifiRepository(
- wifiRepository: WifiRepositoryDagger,
- wifiRepositoryFromTrackerLib: WifiRepositoryViaTrackerLibDagger,
- flags: FeatureFlags,
- ): RealWifiRepository {
- return if (flags.isEnabled(Flags.WIFI_TRACKER_LIB_FOR_WIFI_ICON)) {
- wifiRepositoryFromTrackerLib
- } else {
- wifiRepository
- }
- }
-
- @Provides
- @SysUISingleton
@Named(FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON)
fun provideFirstMobileSubShowingNetworkTypeIconProvider(
mobileIconsViewModel: MobileIconsViewModel,
@@ -197,16 +158,8 @@ abstract class StatusBarPipelineModule {
@Provides
@SysUISingleton
@WifiInputLog
- fun provideWifiInputLogBuffer(factory: LogBufferFactory): LogBuffer {
- return factory.create("WifiInputLog", 50)
- }
-
- @Provides
- @SysUISingleton
- @WifiTrackerLibInputLog
- fun provideWifiTrackerLibInputLogBuffer(factory: LogBufferFactory): LogBuffer {
- // WifiTrackerLib is pretty noisy, so give it more room than WifiInputLog.
- return factory.create("WifiTrackerLibInputLog", 200)
+ fun provideWifiLogBuffer(factory: LogBufferFactory): LogBuffer {
+ return factory.create("WifiInputLog", 200)
}
@Provides
@@ -218,13 +171,6 @@ abstract class StatusBarPipelineModule {
@Provides
@SysUISingleton
- @WifiTrackerLibTableLog
- fun provideWifiTrackerLibTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
- return factory.create("WifiTrackerLibTableLog", 100)
- }
-
- @Provides
- @SysUISingleton
@AirplaneTableLog
fun provideAirplaneTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
return factory.create("AirplaneTableLog", 30)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiInputLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiInputLog.kt
index 6db694474f5f..9ba802c7ee9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiInputLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiInputLog.kt
@@ -18,8 +18,8 @@ package com.android.systemui.statusbar.pipeline.dagger
import javax.inject.Qualifier
-/** Wifi logs for inputs into the wifi pipeline. */
-@Qualifier
-@MustBeDocumented
-@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
-annotation class WifiInputLog
+/**
+ * Wifi logs for inputs into
+ * [com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl].
+ */
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class WifiInputLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index b22e09e9ba40..fc7a67233bb6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository
-import com.android.systemui.CoreStartable
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
@@ -80,8 +79,3 @@ interface WifiRepository {
* repository.
*/
interface RealWifiRepository : WifiRepository
-
-/** Used only by Dagger to bind [WifiRepositoryImpl]. */
-interface WifiRepositoryDagger : RealWifiRepository, CoreStartable
-/** Used only by Dagger to bind [WifiRepositoryViaTrackerLib]. */
-interface WifiRepositoryViaTrackerLibDagger : RealWifiRepository
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
index ca042e26b20b..af6e8a0410ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
@@ -25,7 +25,6 @@ import com.android.systemui.demomode.DemoMode
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoWifiRepository
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
index cfdbe4a9bb6e..b79fb9b5caf3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
@@ -18,8 +18,7 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryViaTrackerLibDagger
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import javax.inject.Inject
@@ -34,10 +33,7 @@ import kotlinx.coroutines.flow.asStateFlow
* wifi information.
*/
@SysUISingleton
-class DisabledWifiRepository @Inject constructor() :
- WifiRepositoryDagger, WifiRepositoryViaTrackerLibDagger {
- override fun start() {}
-
+class DisabledWifiRepository @Inject constructor() : RealWifiRepository {
override val isWifiEnabled: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
override val isWifiDefault: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt
deleted file mode 100644
index 67dd32f473e0..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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.statusbar.pipeline.wifi.data.repository.prod
-
-import android.annotation.SuppressLint
-import android.net.wifi.ScanResult
-import android.net.wifi.WifiManager
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.log.table.logDiffsForTable
-import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
-import java.util.concurrent.Executor
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.asExecutor
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.stateIn
-
-/**
- * Object to provide shared helper functions between [WifiRepositoryImpl] and
- * [WifiRepositoryViaTrackerLib].
- */
-object WifiRepositoryHelper {
- /** Creates a flow that fetches the [DataActivityModel] from [WifiManager]. */
- fun createActivityFlow(
- wifiManager: WifiManager,
- @Main mainExecutor: Executor,
- scope: CoroutineScope,
- tableLogBuffer: TableLogBuffer,
- inputLogger: (String) -> Unit,
- ): StateFlow<DataActivityModel> {
- return conflatedCallbackFlow {
- val callback =
- WifiManager.TrafficStateCallback { state ->
- inputLogger.invoke(prettyPrintActivity(state))
- trySend(state.toWifiDataActivityModel())
- }
- wifiManager.registerTrafficStateCallback(mainExecutor, callback)
- awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
- }
- .logDiffsForTable(
- tableLogBuffer,
- columnPrefix = ACTIVITY_PREFIX,
- initialValue = ACTIVITY_DEFAULT,
- )
- .stateIn(
- scope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = ACTIVITY_DEFAULT,
- )
- }
-
- /**
- * Creates a flow that listens for new [ScanResult]s from [WifiManager]. Does not request a scan
- */
- fun createNetworkScanFlow(
- wifiManager: WifiManager,
- scope: CoroutineScope,
- @Background dispatcher: CoroutineDispatcher,
- inputLogger: () -> Unit,
- ): StateFlow<List<WifiScanEntry>> {
- return conflatedCallbackFlow {
- val callback =
- object : WifiManager.ScanResultsCallback() {
- @SuppressLint("MissingPermission")
- override fun onScanResultsAvailable() {
- inputLogger.invoke()
- trySend(wifiManager.scanResults.toModel())
- }
- }
-
- wifiManager.registerScanResultsCallback(dispatcher.asExecutor(), callback)
-
- awaitClose { wifiManager.unregisterScanResultsCallback(callback) }
- }
- .stateIn(scope, SharingStarted.Eagerly, emptyList())
- }
-
- private fun List<ScanResult>.toModel(): List<WifiScanEntry> = map { WifiScanEntry(it.SSID) }
-
- // TODO(b/292534484): This print should only be done in [MessagePrinter] part of the log buffer.
- private fun prettyPrintActivity(activity: Int): String {
- return when (activity) {
- WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE -> "NONE"
- WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN -> "IN"
- WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT -> "OUT"
- WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT -> "INOUT"
- else -> "INVALID"
- }
- }
-
- private const val ACTIVITY_PREFIX = "wifiActivity"
- val ACTIVITY_DEFAULT = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index 59ef8846e281..20e44e7cf924 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 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.
@@ -17,309 +17,447 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod
import android.annotation.SuppressLint
-import android.content.IntentFilter
-import android.net.ConnectivityManager
-import android.net.Network
-import android.net.NetworkCapabilities
-import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN
-import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
-import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
-import android.net.NetworkCapabilities.TRANSPORT_WIFI
-import android.net.NetworkRequest
-import android.net.wifi.WifiInfo
+import android.net.wifi.ScanResult
import android.net.wifi.WifiManager
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
-import com.android.systemui.broadcast.BroadcastDispatcher
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
+import com.android.internal.annotations.VisibleForTesting
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.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
+import com.android.systemui.statusbar.pipeline.dagger.WifiInputLog
import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
-import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.getMainOrUnderlyingWifiInfo
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.CARRIER_MERGED_INVALID_SUB_ID_REASON
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_ENABLED
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger
-import com.android.systemui.statusbar.pipeline.wifi.shared.WifiInputLogger
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Inactive.toHotspotDeviceType
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
+import com.android.wifitrackerlib.HotspotNetworkEntry
+import com.android.wifitrackerlib.MergedCarrierEntry
+import com.android.wifitrackerlib.WifiEntry
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE
+import com.android.wifitrackerlib.WifiPickerTracker
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-/** Real implementation of [WifiRepository]. */
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
+/**
+ * A real implementation of [WifiRepository] that uses [com.android.wifitrackerlib] as the source of
+ * truth for wifi information.
+ */
@SysUISingleton
-@SuppressLint("MissingPermission")
class WifiRepositoryImpl
@Inject
constructor(
- broadcastDispatcher: BroadcastDispatcher,
- connectivityManager: ConnectivityManager,
- connectivityRepository: ConnectivityRepository,
- logger: WifiInputLogger,
- @WifiTableLog wifiTableLogBuffer: TableLogBuffer,
- @Main mainExecutor: Executor,
- @Background private val bgDispatcher: CoroutineDispatcher,
+ featureFlags: FeatureFlags,
@Application private val scope: CoroutineScope,
+ @Main private val mainExecutor: Executor,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ private val wifiPickerTrackerFactory: WifiPickerTrackerFactory,
private val wifiManager: WifiManager,
-) : WifiRepositoryDagger {
-
- override fun start() {
- // There are two possible [WifiRepository] implementations: This class (old) and
- // [WifiRepositoryFromTrackerLib] (new). While we migrate to the new class, we want this old
- // class to still be running in the background so that we can collect logs and compare
- // discrepancies. This #start method collects on the flows to ensure that the logs are
- // collected.
- scope.launch { isWifiEnabled.collect {} }
- scope.launch { isWifiDefault.collect {} }
- scope.launch { wifiNetwork.collect {} }
- scope.launch { wifiActivity.collect {} }
- }
+ @WifiInputLog private val inputLogger: LogBuffer,
+ @WifiTableLog private val tableLogger: TableLogBuffer,
+) : RealWifiRepository, LifecycleOwner {
- private val wifiStateChangeEvents: Flow<Unit> =
- broadcastDispatcher
- .broadcastFlow(IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION))
- .onEach { logger.logIntent("WIFI_STATE_CHANGED_ACTION") }
+ override val lifecycle =
+ LifecycleRegistry(this).also {
+ mainExecutor.execute { it.currentState = Lifecycle.State.CREATED }
+ }
+
+ private val isInstantTetherEnabled = featureFlags.isEnabled(Flags.INSTANT_TETHER)
- private val wifiNetworkChangeEvents: MutableSharedFlow<Unit> =
- MutableSharedFlow(extraBufferCapacity = 1)
+ private var wifiPickerTracker: WifiPickerTracker? = null
+
+ private val wifiPickerTrackerInfo: StateFlow<WifiPickerTrackerInfo> = run {
+ var current =
+ WifiPickerTrackerInfo(
+ state = WIFI_STATE_DEFAULT,
+ isDefault = false,
+ primaryNetwork = WIFI_NETWORK_DEFAULT,
+ secondaryNetworks = emptyList(),
+ )
+ callbackFlow {
+ val callback =
+ object : WifiPickerTracker.WifiPickerTrackerCallback {
+ override fun onWifiEntriesChanged() {
+ val connectedEntry = wifiPickerTracker.mergedOrPrimaryConnection
+ logOnWifiEntriesChanged(connectedEntry)
+
+ val secondaryNetworks =
+ if (featureFlags.isEnabled(Flags.WIFI_SECONDARY_NETWORKS)) {
+ val activeNetworks =
+ wifiPickerTracker?.activeWifiEntries ?: emptyList()
+ activeNetworks
+ .filter { it != connectedEntry && !it.isPrimaryNetwork }
+ .map { it.toWifiNetworkModel() }
+ } else {
+ emptyList()
+ }
+
+ // [WifiPickerTracker.connectedWifiEntry] will return the same instance
+ // but with updated internals. For example, when its validation status
+ // changes from false to true, the same instance is re-used but with the
+ // validated field updated.
+ //
+ // Because it's the same instance, the flow won't re-emit the value
+ // (even though the internals have changed). So, we need to transform it
+ // into our internal model immediately. [toWifiNetworkModel] always
+ // returns a new instance, so the flow is guaranteed to emit.
+ send(
+ newPrimaryNetwork = connectedEntry?.toPrimaryWifiNetworkModel()
+ ?: WIFI_NETWORK_DEFAULT,
+ newSecondaryNetworks = secondaryNetworks,
+ newIsDefault = connectedEntry?.isDefaultNetwork ?: false,
+ )
+ }
+
+ override fun onWifiStateChanged() {
+ val state = wifiPickerTracker?.wifiState
+ logOnWifiStateChanged(state)
+ send(newState = state ?: WIFI_STATE_DEFAULT)
+ }
+
+ override fun onNumSavedNetworksChanged() {}
+
+ override fun onNumSavedSubscriptionsChanged() {}
+
+ private fun send(
+ newState: Int = current.state,
+ newIsDefault: Boolean = current.isDefault,
+ newPrimaryNetwork: WifiNetworkModel = current.primaryNetwork,
+ newSecondaryNetworks: List<WifiNetworkModel> =
+ current.secondaryNetworks,
+ ) {
+ val new =
+ WifiPickerTrackerInfo(
+ newState,
+ newIsDefault,
+ newPrimaryNetwork,
+ newSecondaryNetworks,
+ )
+ current = new
+ trySend(new)
+ }
+ }
+
+ wifiPickerTracker =
+ wifiPickerTrackerFactory.create(lifecycle, callback, "WifiRepository").apply {
+ // By default, [WifiPickerTracker] will scan to see all available wifi
+ // networks in the area. Because SysUI only needs to display the
+ // **connected** network, we don't need scans to be running (and in fact,
+ // running scans is costly and should be avoided whenever possible).
+ this?.disableScanning()
+ }
+ // The lifecycle must be STARTED in order for the callback to receive events.
+ mainExecutor.execute { lifecycle.currentState = Lifecycle.State.STARTED }
+ awaitClose {
+ mainExecutor.execute { lifecycle.currentState = Lifecycle.State.CREATED }
+ }
+ }
+ .stateIn(scope, SharingStarted.Eagerly, current)
+ }
- // Because [WifiManager] doesn't expose a wifi enabled change listener, we do it
- // internally by fetching [WifiManager.isWifiEnabled] whenever we think the state may
- // have changed.
override val isWifiEnabled: StateFlow<Boolean> =
- merge(wifiNetworkChangeEvents, wifiStateChangeEvents)
- .onStart { emit(Unit) }
- .mapLatest { isWifiEnabled() }
+ wifiPickerTrackerInfo
+ .map { it.state == WifiManager.WIFI_STATE_ENABLED }
.distinctUntilChanged()
.logDiffsForTable(
- wifiTableLogBuffer,
+ tableLogger,
columnPrefix = "",
columnName = COL_NAME_IS_ENABLED,
initialValue = false,
)
- .stateIn(
- scope = scope,
- started = SharingStarted.Eagerly,
- initialValue = false,
- )
+ .stateIn(scope, SharingStarted.Eagerly, false)
- // [WifiManager.isWifiEnabled] is a blocking IPC call, so fetch it in the background.
- private suspend fun isWifiEnabled(): Boolean =
- withContext(bgDispatcher) { wifiManager.isWifiEnabled }
+ override val wifiNetwork: StateFlow<WifiNetworkModel> =
+ wifiPickerTrackerInfo
+ .map { it.primaryNetwork }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogger,
+ columnPrefix = "",
+ initialValue = WIFI_NETWORK_DEFAULT,
+ )
+ .stateIn(scope, SharingStarted.Eagerly, WIFI_NETWORK_DEFAULT)
- override val isWifiDefault: StateFlow<Boolean> =
- connectivityRepository.defaultConnections
- // TODO(b/274493701): Should wifi be considered default if it's carrier merged?
- .map { it.wifi.isDefault || it.carrierMerged.isDefault }
+ override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
+ wifiPickerTrackerInfo
+ .map { it.secondaryNetworks }
.distinctUntilChanged()
.logDiffsForTable(
- wifiTableLogBuffer,
+ tableLogger,
columnPrefix = "",
- columnName = COL_NAME_IS_DEFAULT,
- initialValue = false,
+ columnName = "secondaryNetworks",
+ initialValue = emptyList(),
)
- .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+ .stateIn(scope, SharingStarted.Eagerly, emptyList())
- override val wifiNetwork: StateFlow<WifiNetworkModel> =
- conflatedCallbackFlow {
- var currentWifi: WifiNetworkModel = WIFI_NETWORK_DEFAULT
+ /**
+ * [WifiPickerTracker.getConnectedWifiEntry] stores a [MergedCarrierEntry] separately from the
+ * [WifiEntry] for the primary connection. Therefore, we have to prefer the carrier-merged entry
+ * if it exists, falling back on the connected entry if null
+ */
+ private val WifiPickerTracker?.mergedOrPrimaryConnection: WifiEntry?
+ get() {
+ val mergedEntry: MergedCarrierEntry? = this?.mergedCarrierEntry
+ return if (mergedEntry != null && mergedEntry.isDefaultNetwork) {
+ mergedEntry
+ } else {
+ this?.connectedWifiEntry
+ }
+ }
- val callback =
- object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
- override fun onCapabilitiesChanged(
- network: Network,
- networkCapabilities: NetworkCapabilities
- ) {
- logger.logOnCapabilitiesChanged(
- network,
- networkCapabilities,
- isDefaultNetworkCallback = false,
- )
+ /**
+ * Converts WifiTrackerLib's [WifiEntry] into our internal model only if the entry is the
+ * primary network. Returns an inactive network if it's not primary.
+ */
+ private fun WifiEntry.toPrimaryWifiNetworkModel(): WifiNetworkModel {
+ return if (!this.isPrimaryNetwork) {
+ WIFI_NETWORK_DEFAULT
+ } else {
+ this.toWifiNetworkModel()
+ }
+ }
- wifiNetworkChangeEvents.tryEmit(Unit)
-
- val wifiInfo =
- networkCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
- if (wifiInfo?.isPrimary == true) {
- val wifiNetworkModel =
- createWifiNetworkModel(
- wifiInfo,
- network,
- networkCapabilities,
- wifiManager,
- )
- currentWifi = wifiNetworkModel
- trySend(wifiNetworkModel)
- }
- }
+ /** Converts WifiTrackerLib's [WifiEntry] into our internal model. */
+ private fun WifiEntry.toWifiNetworkModel(): WifiNetworkModel {
+ return if (this is MergedCarrierEntry) {
+ this.convertCarrierMergedToModel()
+ } else {
+ this.convertNormalToModel()
+ }
+ }
- override fun onLost(network: Network) {
- logger.logOnLost(network, isDefaultNetworkCallback = false)
-
- wifiNetworkChangeEvents.tryEmit(Unit)
-
- val wifi = currentWifi
- if (
- (wifi is WifiNetworkModel.Active &&
- wifi.networkId == network.getNetId()) ||
- (wifi is WifiNetworkModel.CarrierMerged &&
- wifi.networkId == network.getNetId())
- ) {
- val newNetworkModel = WifiNetworkModel.Inactive
- currentWifi = newNetworkModel
- trySend(newNetworkModel)
- }
- }
- }
+ private fun MergedCarrierEntry.convertCarrierMergedToModel(): WifiNetworkModel {
+ return if (this.subscriptionId == INVALID_SUBSCRIPTION_ID) {
+ WifiNetworkModel.Invalid(CARRIER_MERGED_INVALID_SUB_ID_REASON)
+ } else {
+ WifiNetworkModel.CarrierMerged(
+ networkId = NETWORK_ID,
+ subscriptionId = this.subscriptionId,
+ level = this.level,
+ // WifiManager APIs to calculate the signal level start from 0, so
+ // maxSignalLevel + 1 represents the total level buckets count.
+ numberOfLevels = wifiManager.maxSignalLevel + 1,
+ )
+ }
+ }
- connectivityManager.registerNetworkCallback(WIFI_NETWORK_CALLBACK_REQUEST, callback)
+ private fun WifiEntry.convertNormalToModel(): WifiNetworkModel {
+ if (this.level == WIFI_LEVEL_UNREACHABLE || this.level !in WIFI_LEVEL_MIN..WIFI_LEVEL_MAX) {
+ // If our level means the network is unreachable or the level is otherwise invalid, we
+ // don't have an active network.
+ return WifiNetworkModel.Inactive
+ }
- awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
+ val hotspotDeviceType =
+ if (isInstantTetherEnabled && this is HotspotNetworkEntry) {
+ this.deviceType.toHotspotDeviceType()
+ } else {
+ WifiNetworkModel.HotspotDeviceType.NONE
}
+
+ return WifiNetworkModel.Active(
+ networkId = NETWORK_ID,
+ isValidated = this.hasInternetAccess(),
+ level = this.level,
+ ssid = this.title,
+ hotspotDeviceType = hotspotDeviceType,
+ // With WifiTrackerLib, [WifiEntry.title] will appropriately fetch the SSID for
+ // typical wifi networks *and* passpoint/OSU APs. So, the AP-specific values can
+ // always be false/null in this repository.
+ // TODO(b/292534484): Remove these fields from the wifi network model once this
+ // repository is fully enabled.
+ isPasspointAccessPoint = false,
+ isOnlineSignUpForPasspointAccessPoint = false,
+ passpointProviderFriendlyName = null,
+ )
+ }
+
+ override val isWifiDefault: StateFlow<Boolean> =
+ wifiPickerTrackerInfo
+ .map { it.isDefault }
.distinctUntilChanged()
.logDiffsForTable(
- wifiTableLogBuffer,
+ tableLogger,
columnPrefix = "",
- initialValue = WIFI_NETWORK_DEFAULT,
+ columnName = COL_NAME_IS_DEFAULT,
+ initialValue = false,
+ )
+ .stateIn(scope, SharingStarted.Eagerly, false)
+
+ override val wifiActivity: StateFlow<DataActivityModel> =
+ conflatedCallbackFlow {
+ val callback =
+ WifiManager.TrafficStateCallback { state ->
+ logActivity(state)
+ trySend(state.toWifiDataActivityModel())
+ }
+ wifiManager.registerTrafficStateCallback(mainExecutor, callback)
+ awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
+ }
+ .logDiffsForTable(
+ tableLogger,
+ columnPrefix = ACTIVITY_PREFIX,
+ initialValue = ACTIVITY_DEFAULT,
)
- // There will be multiple wifi icons in different places that will frequently
- // subscribe/unsubscribe to flows as the views attach/detach. Using [stateIn] ensures
- // that new subscribes will get the latest value immediately upon subscription.
- // Otherwise, the views could show stale data. See b/244173280.
.stateIn(
scope,
started = SharingStarted.WhileSubscribed(),
- initialValue = WIFI_NETWORK_DEFAULT,
+ initialValue = ACTIVITY_DEFAULT,
)
- // Secondary networks can only be supported by [WifiRepositoryViaTrackerLib].
- override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
- MutableStateFlow(emptyList<WifiNetworkModel>()).asStateFlow()
+ override val wifiScanResults: StateFlow<List<WifiScanEntry>> =
+ conflatedCallbackFlow {
+ val callback =
+ object : WifiManager.ScanResultsCallback() {
+ @SuppressLint("MissingPermission")
+ override fun onScanResultsAvailable() {
+ logScanResults()
+ trySend(wifiManager.scanResults.toModel())
+ }
+ }
- override val wifiActivity: StateFlow<DataActivityModel> =
- WifiRepositoryHelper.createActivityFlow(
- wifiManager,
- mainExecutor,
- scope,
- wifiTableLogBuffer,
- logger::logActivity,
+ wifiManager.registerScanResultsCallback(bgDispatcher.asExecutor(), callback)
+
+ awaitClose { wifiManager.unregisterScanResultsCallback(callback) }
+ }
+ .stateIn(scope, SharingStarted.Eagerly, emptyList())
+
+ private fun List<ScanResult>.toModel(): List<WifiScanEntry> = map { WifiScanEntry(it.SSID) }
+
+ private fun logOnWifiEntriesChanged(connectedEntry: WifiEntry?) {
+ inputLogger.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = connectedEntry.toString() },
+ { "onWifiEntriesChanged. ConnectedEntry=$str1" },
)
+ }
- override val wifiScanResults: StateFlow<List<WifiScanEntry>> =
- WifiRepositoryHelper.createNetworkScanFlow(
- wifiManager,
- scope,
- bgDispatcher,
- logger::logScanResults
+ private fun logOnWifiStateChanged(state: Int?) {
+ inputLogger.log(
+ TAG,
+ LogLevel.DEBUG,
+ { int1 = state ?: -1 },
+ { "onWifiStateChanged. State=${if (int1 == -1) null else int1}" },
)
+ }
- companion object {
- // Start out with no known wifi network.
- // Note: [WifiStatusTracker] (the old implementation of connectivity logic) does do an
- // initial fetch to get a starting wifi network. But, it uses a deprecated API
- // [WifiManager.getConnectionInfo()], and the deprecation doc indicates to just use
- // [ConnectivityManager.NetworkCallback] results instead. So, for now we'll just rely on the
- // NetworkCallback inside [wifiNetwork] for our wifi network information.
- val WIFI_NETWORK_DEFAULT = WifiNetworkModel.Inactive
-
- const val WIFI_STATE_DEFAULT = WifiManager.WIFI_STATE_DISABLED
-
- private fun createWifiNetworkModel(
- wifiInfo: WifiInfo,
- network: Network,
- networkCapabilities: NetworkCapabilities,
- wifiManager: WifiManager,
- ): WifiNetworkModel {
- return if (wifiInfo.isCarrierMerged) {
- if (wifiInfo.subscriptionId == INVALID_SUBSCRIPTION_ID) {
- WifiNetworkModel.Invalid(CARRIER_MERGED_INVALID_SUB_ID_REASON)
- } else {
- WifiNetworkModel.CarrierMerged(
- networkId = network.getNetId(),
- subscriptionId = wifiInfo.subscriptionId,
- level = wifiManager.calculateSignalLevel(wifiInfo.rssi),
- // The WiFi signal level returned by WifiManager#calculateSignalLevel start
- // from 0, so WifiManager#getMaxSignalLevel + 1 represents the total level
- // buckets count.
- numberOfLevels = wifiManager.maxSignalLevel + 1,
- )
- }
- } else {
- WifiNetworkModel.Active(
- network.getNetId(),
- isValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED),
- level = wifiManager.calculateSignalLevel(wifiInfo.rssi),
- wifiInfo.ssid,
- // This repository doesn't support any hotspot information.
- WifiNetworkModel.HotspotDeviceType.NONE,
- wifiInfo.isPasspointAp,
- wifiInfo.isOsuAp,
- wifiInfo.passpointProviderFriendlyName
- )
- }
- }
+ private fun logActivity(activity: Int) {
+ inputLogger.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = prettyPrintActivity(activity) },
+ { "onActivityChanged: $str1" }
+ )
+ }
- private val WIFI_NETWORK_CALLBACK_REQUEST: NetworkRequest =
- NetworkRequest.Builder()
- .clearCapabilities()
- .addCapability(NET_CAPABILITY_NOT_VPN)
- .addTransportType(TRANSPORT_WIFI)
- .addTransportType(TRANSPORT_CELLULAR)
- .build()
+ // TODO(b/292534484): This print should only be done in [MessagePrinter] part of the log buffer.
+ private fun prettyPrintActivity(activity: Int): String {
+ return when (activity) {
+ WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE -> "NONE"
+ WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN -> "IN"
+ WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT -> "OUT"
+ WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT -> "INOUT"
+ else -> "INVALID"
+ }
}
+ private fun logScanResults() =
+ inputLogger.log(TAG, LogLevel.DEBUG, {}, { "onScanResultsAvailable" })
+
+ /**
+ * Data class storing all the information fetched from [WifiPickerTracker].
+ *
+ * Used so that we only register a single callback on [WifiPickerTracker].
+ */
+ data class WifiPickerTrackerInfo(
+ /** The current wifi state. See [WifiManager.getWifiState]. */
+ val state: Int,
+ /** True if wifi is currently the default connection and false otherwise. */
+ val isDefault: Boolean,
+ /** The currently primary wifi network. */
+ val primaryNetwork: WifiNetworkModel,
+ /** The current secondary network(s), if any. Specifically excludes the primary network. */
+ val secondaryNetworks: List<WifiNetworkModel>
+ )
+
@SysUISingleton
class Factory
@Inject
constructor(
- private val broadcastDispatcher: BroadcastDispatcher,
- private val connectivityManager: ConnectivityManager,
- private val connectivityRepository: ConnectivityRepository,
- private val logger: WifiInputLogger,
- @WifiTableLog private val wifiTableLogBuffer: TableLogBuffer,
+ private val featureFlags: FeatureFlags,
+ @Application private val scope: CoroutineScope,
@Main private val mainExecutor: Executor,
@Background private val bgDispatcher: CoroutineDispatcher,
- @Application private val scope: CoroutineScope,
+ private val wifiPickerTrackerFactory: WifiPickerTrackerFactory,
+ @WifiInputLog private val inputLogger: LogBuffer,
+ @WifiTableLog private val tableLogger: TableLogBuffer,
) {
fun create(wifiManager: WifiManager): WifiRepositoryImpl {
return WifiRepositoryImpl(
- broadcastDispatcher,
- connectivityManager,
- connectivityRepository,
- logger,
- wifiTableLogBuffer,
+ featureFlags,
+ scope,
mainExecutor,
bgDispatcher,
- scope,
+ wifiPickerTrackerFactory,
wifiManager,
+ inputLogger,
+ tableLogger,
)
}
}
+
+ companion object {
+ // Start out with no known wifi network.
+ @VisibleForTesting val WIFI_NETWORK_DEFAULT = WifiNetworkModel.Inactive
+
+ private const val WIFI_STATE_DEFAULT = WifiManager.WIFI_STATE_DISABLED
+
+ private const val ACTIVITY_PREFIX = "wifiActivity"
+ val ACTIVITY_DEFAULT = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+
+ private const val TAG = "WifiTrackerLibInputLog"
+
+ /**
+ * [WifiNetworkModel.Active.networkId] is only used at the repository layer. It's used by
+ * [WifiRepositoryImpl], which tracks the ID in order to correctly apply the framework
+ * callbacks within the repository.
+ *
+ * Since this class does not need to manually apply framework callbacks and since the
+ * network ID is not used beyond the repository, it's safe to use an invalid ID in this
+ * repository.
+ *
+ * The [WifiNetworkModel.Active.networkId] field should be deleted once we've fully migrated
+ * to [WifiRepositoryImpl].
+ */
+ private const val NETWORK_ID = -1
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
deleted file mode 100644
index 1670dd39ba24..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
+++ /dev/null
@@ -1,414 +0,0 @@
-/*
- * 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.statusbar.pipeline.wifi.data.repository.prod
-
-import android.net.wifi.WifiManager
-import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.LifecycleRegistry
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.core.LogLevel
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.log.table.logDiffsForTable
-import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
-import com.android.systemui.statusbar.pipeline.dagger.WifiTrackerLibInputLog
-import com.android.systemui.statusbar.pipeline.dagger.WifiTrackerLibTableLog
-import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.CARRIER_MERGED_INVALID_SUB_ID_REASON
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_DEFAULT
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_ENABLED
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryViaTrackerLibDagger
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_STATE_DEFAULT
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Inactive.toHotspotDeviceType
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
-import com.android.wifitrackerlib.HotspotNetworkEntry
-import com.android.wifitrackerlib.MergedCarrierEntry
-import com.android.wifitrackerlib.WifiEntry
-import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX
-import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN
-import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE
-import com.android.wifitrackerlib.WifiPickerTracker
-import java.util.concurrent.Executor
-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.callbackFlow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
-
-/**
- * An implementation of [WifiRepository] that uses [com.android.wifitrackerlib] as the source of
- * truth for wifi information.
- *
- * Serves as a possible replacement for [WifiRepositoryImpl]. See b/292534484.
- */
-@SysUISingleton
-class WifiRepositoryViaTrackerLib
-@Inject
-constructor(
- featureFlags: FeatureFlags,
- @Application private val scope: CoroutineScope,
- @Main private val mainExecutor: Executor,
- @Background private val bgDispatcher: CoroutineDispatcher,
- private val wifiPickerTrackerFactory: WifiPickerTrackerFactory,
- private val wifiManager: WifiManager,
- @WifiTrackerLibInputLog private val inputLogger: LogBuffer,
- @WifiTrackerLibTableLog private val wifiTrackerLibTableLogBuffer: TableLogBuffer,
-) : WifiRepositoryViaTrackerLibDagger, LifecycleOwner {
-
- override val lifecycle =
- LifecycleRegistry(this).also {
- mainExecutor.execute { it.currentState = Lifecycle.State.CREATED }
- }
-
- private val isInstantTetherEnabled = featureFlags.isEnabled(Flags.INSTANT_TETHER)
-
- private var wifiPickerTracker: WifiPickerTracker? = null
-
- private val wifiPickerTrackerInfo: StateFlow<WifiPickerTrackerInfo> = run {
- var current =
- WifiPickerTrackerInfo(
- state = WIFI_STATE_DEFAULT,
- isDefault = false,
- primaryNetwork = WIFI_NETWORK_DEFAULT,
- secondaryNetworks = emptyList(),
- )
- callbackFlow {
- val callback =
- object : WifiPickerTracker.WifiPickerTrackerCallback {
- override fun onWifiEntriesChanged() {
- val connectedEntry = wifiPickerTracker.mergedOrPrimaryConnection
- logOnWifiEntriesChanged(connectedEntry)
-
- val secondaryNetworks =
- if (featureFlags.isEnabled(Flags.WIFI_SECONDARY_NETWORKS)) {
- val activeNetworks =
- wifiPickerTracker?.activeWifiEntries ?: emptyList()
- activeNetworks
- .filter { it != connectedEntry && !it.isPrimaryNetwork }
- .map { it.toWifiNetworkModel() }
- } else {
- emptyList()
- }
-
- // [WifiPickerTracker.connectedWifiEntry] will return the same instance
- // but with updated internals. For example, when its validation status
- // changes from false to true, the same instance is re-used but with the
- // validated field updated.
- //
- // Because it's the same instance, the flow won't re-emit the value
- // (even though the internals have changed). So, we need to transform it
- // into our internal model immediately. [toWifiNetworkModel] always
- // returns a new instance, so the flow is guaranteed to emit.
- send(
- newPrimaryNetwork = connectedEntry?.toPrimaryWifiNetworkModel()
- ?: WIFI_NETWORK_DEFAULT,
- newSecondaryNetworks = secondaryNetworks,
- newIsDefault = connectedEntry?.isDefaultNetwork ?: false,
- )
- }
-
- override fun onWifiStateChanged() {
- val state = wifiPickerTracker?.wifiState
- logOnWifiStateChanged(state)
- send(newState = state ?: WIFI_STATE_DEFAULT)
- }
-
- override fun onNumSavedNetworksChanged() {}
-
- override fun onNumSavedSubscriptionsChanged() {}
-
- private fun send(
- newState: Int = current.state,
- newIsDefault: Boolean = current.isDefault,
- newPrimaryNetwork: WifiNetworkModel = current.primaryNetwork,
- newSecondaryNetworks: List<WifiNetworkModel> =
- current.secondaryNetworks,
- ) {
- val new =
- WifiPickerTrackerInfo(
- newState,
- newIsDefault,
- newPrimaryNetwork,
- newSecondaryNetworks,
- )
- current = new
- trySend(new)
- }
- }
-
- wifiPickerTracker =
- wifiPickerTrackerFactory.create(lifecycle, callback, "WifiRepository").apply {
- // By default, [WifiPickerTracker] will scan to see all available wifi
- // networks in the area. Because SysUI only needs to display the
- // **connected** network, we don't need scans to be running (and in fact,
- // running scans is costly and should be avoided whenever possible).
- this?.disableScanning()
- }
- // The lifecycle must be STARTED in order for the callback to receive events.
- mainExecutor.execute { lifecycle.currentState = Lifecycle.State.STARTED }
- awaitClose {
- mainExecutor.execute { lifecycle.currentState = Lifecycle.State.CREATED }
- }
- }
- .stateIn(scope, SharingStarted.Eagerly, current)
- }
-
- override val isWifiEnabled: StateFlow<Boolean> =
- wifiPickerTrackerInfo
- .map { it.state == WifiManager.WIFI_STATE_ENABLED }
- .distinctUntilChanged()
- .logDiffsForTable(
- wifiTrackerLibTableLogBuffer,
- columnPrefix = "",
- columnName = COL_NAME_IS_ENABLED,
- initialValue = false,
- )
- .stateIn(scope, SharingStarted.Eagerly, false)
-
- override val wifiNetwork: StateFlow<WifiNetworkModel> =
- wifiPickerTrackerInfo
- .map { it.primaryNetwork }
- .distinctUntilChanged()
- .logDiffsForTable(
- wifiTrackerLibTableLogBuffer,
- columnPrefix = "",
- initialValue = WIFI_NETWORK_DEFAULT,
- )
- .stateIn(scope, SharingStarted.Eagerly, WIFI_NETWORK_DEFAULT)
-
- override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
- wifiPickerTrackerInfo
- .map { it.secondaryNetworks }
- .distinctUntilChanged()
- .logDiffsForTable(
- wifiTrackerLibTableLogBuffer,
- columnPrefix = "",
- columnName = "secondaryNetworks",
- initialValue = emptyList(),
- )
- .stateIn(scope, SharingStarted.Eagerly, emptyList())
-
- /**
- * [WifiPickerTracker.getConnectedWifiEntry] stores a [MergedCarrierEntry] separately from the
- * [WifiEntry] for the primary connection. Therefore, we have to prefer the carrier-merged entry
- * if it exists, falling back on the connected entry if null
- */
- private val WifiPickerTracker?.mergedOrPrimaryConnection: WifiEntry?
- get() {
- val mergedEntry: MergedCarrierEntry? = this?.mergedCarrierEntry
- return if (mergedEntry != null && mergedEntry.isDefaultNetwork) {
- mergedEntry
- } else {
- this?.connectedWifiEntry
- }
- }
-
- /**
- * Converts WifiTrackerLib's [WifiEntry] into our internal model only if the entry is the
- * primary network. Returns an inactive network if it's not primary.
- */
- private fun WifiEntry.toPrimaryWifiNetworkModel(): WifiNetworkModel {
- return if (!this.isPrimaryNetwork) {
- WIFI_NETWORK_DEFAULT
- } else {
- this.toWifiNetworkModel()
- }
- }
-
- /** Converts WifiTrackerLib's [WifiEntry] into our internal model. */
- private fun WifiEntry.toWifiNetworkModel(): WifiNetworkModel {
- return if (this is MergedCarrierEntry) {
- this.convertCarrierMergedToModel()
- } else {
- this.convertNormalToModel()
- }
- }
-
- private fun MergedCarrierEntry.convertCarrierMergedToModel(): WifiNetworkModel {
- return if (this.subscriptionId == INVALID_SUBSCRIPTION_ID) {
- WifiNetworkModel.Invalid(CARRIER_MERGED_INVALID_SUB_ID_REASON)
- } else {
- WifiNetworkModel.CarrierMerged(
- networkId = NETWORK_ID,
- subscriptionId = this.subscriptionId,
- level = this.level,
- // WifiManager APIs to calculate the signal level start from 0, so
- // maxSignalLevel + 1 represents the total level buckets count.
- numberOfLevels = wifiManager.maxSignalLevel + 1,
- )
- }
- }
-
- private fun WifiEntry.convertNormalToModel(): WifiNetworkModel {
- if (this.level == WIFI_LEVEL_UNREACHABLE || this.level !in WIFI_LEVEL_MIN..WIFI_LEVEL_MAX) {
- // If our level means the network is unreachable or the level is otherwise invalid, we
- // don't have an active network.
- return WifiNetworkModel.Inactive
- }
-
- val hotspotDeviceType =
- if (isInstantTetherEnabled && this is HotspotNetworkEntry) {
- this.deviceType.toHotspotDeviceType()
- } else {
- WifiNetworkModel.HotspotDeviceType.NONE
- }
-
- return WifiNetworkModel.Active(
- networkId = NETWORK_ID,
- isValidated = this.hasInternetAccess(),
- level = this.level,
- ssid = this.title,
- hotspotDeviceType = hotspotDeviceType,
- // With WifiTrackerLib, [WifiEntry.title] will appropriately fetch the SSID for
- // typical wifi networks *and* passpoint/OSU APs. So, the AP-specific values can
- // always be false/null in this repository.
- // TODO(b/292534484): Remove these fields from the wifi network model once this
- // repository is fully enabled.
- isPasspointAccessPoint = false,
- isOnlineSignUpForPasspointAccessPoint = false,
- passpointProviderFriendlyName = null,
- )
- }
-
- override val isWifiDefault: StateFlow<Boolean> =
- wifiPickerTrackerInfo
- .map { it.isDefault }
- .distinctUntilChanged()
- .logDiffsForTable(
- wifiTrackerLibTableLogBuffer,
- columnPrefix = "",
- columnName = COL_NAME_IS_DEFAULT,
- initialValue = false,
- )
- .stateIn(scope, SharingStarted.Eagerly, false)
-
- override val wifiActivity: StateFlow<DataActivityModel> =
- WifiRepositoryHelper.createActivityFlow(
- wifiManager,
- mainExecutor,
- scope,
- wifiTrackerLibTableLogBuffer,
- this::logActivity,
- )
-
- override val wifiScanResults: StateFlow<List<WifiScanEntry>> =
- WifiRepositoryHelper.createNetworkScanFlow(
- wifiManager,
- scope,
- bgDispatcher,
- this::logScanResults,
- )
-
- private fun logOnWifiEntriesChanged(connectedEntry: WifiEntry?) {
- inputLogger.log(
- TAG,
- LogLevel.DEBUG,
- { str1 = connectedEntry.toString() },
- { "onWifiEntriesChanged. ConnectedEntry=$str1" },
- )
- }
-
- private fun logOnWifiStateChanged(state: Int?) {
- inputLogger.log(
- TAG,
- LogLevel.DEBUG,
- { int1 = state ?: -1 },
- { "onWifiStateChanged. State=${if (int1 == -1) null else int1}" },
- )
- }
-
- private fun logActivity(activity: String) {
- inputLogger.log(TAG, LogLevel.DEBUG, { str1 = activity }, { "onActivityChanged: $str1" })
- }
-
- private fun logScanResults() =
- inputLogger.log(TAG, LogLevel.DEBUG, {}, { "onScanResultsAvailable" })
-
- /**
- * Data class storing all the information fetched from [WifiPickerTracker].
- *
- * Used so that we only register a single callback on [WifiPickerTracker].
- */
- data class WifiPickerTrackerInfo(
- /** The current wifi state. See [WifiManager.getWifiState]. */
- val state: Int,
- /** True if wifi is currently the default connection and false otherwise. */
- val isDefault: Boolean,
- /** The currently primary wifi network. */
- val primaryNetwork: WifiNetworkModel,
- /** The current secondary network(s), if any. Specifically excludes the primary network. */
- val secondaryNetworks: List<WifiNetworkModel>
- )
-
- @SysUISingleton
- class Factory
- @Inject
- constructor(
- private val featureFlags: FeatureFlags,
- @Application private val scope: CoroutineScope,
- @Main private val mainExecutor: Executor,
- @Background private val bgDispatcher: CoroutineDispatcher,
- private val wifiPickerTrackerFactory: WifiPickerTrackerFactory,
- @WifiTrackerLibInputLog private val inputLogger: LogBuffer,
- @WifiTrackerLibTableLog private val wifiTrackerLibTableLogBuffer: TableLogBuffer,
- ) {
- fun create(wifiManager: WifiManager): WifiRepositoryViaTrackerLib {
- return WifiRepositoryViaTrackerLib(
- featureFlags,
- scope,
- mainExecutor,
- bgDispatcher,
- wifiPickerTrackerFactory,
- wifiManager,
- inputLogger,
- wifiTrackerLibTableLogBuffer,
- )
- }
- }
-
- companion object {
- private const val TAG = "WifiTrackerLibInputLog"
-
- /**
- * [WifiNetworkModel.Active.networkId] is only used at the repository layer. It's used by
- * [WifiRepositoryImpl], which tracks the ID in order to correctly apply the framework
- * callbacks within the repository.
- *
- * Since this class does not need to manually apply framework callbacks and since the
- * network ID is not used beyond the repository, it's safe to use an invalid ID in this
- * repository.
- *
- * The [WifiNetworkModel.Active.networkId] field should be deleted once we've fully migrated
- * to [WifiRepositoryViaTrackerLib].
- */
- private const val NETWORK_ID = -1
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
deleted file mode 100644
index b76bb51bfcb8..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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.statusbar.pipeline.wifi.shared
-
-import android.net.Network
-import android.net.NetworkCapabilities
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.core.LogLevel
-import com.android.systemui.statusbar.pipeline.dagger.WifiInputLog
-import com.android.systemui.statusbar.pipeline.shared.LoggerHelper
-import javax.inject.Inject
-
-/**
- * Logger for all the wifi-related inputs (intents, callbacks, etc.) that the wifi repo receives.
- */
-@SysUISingleton
-class WifiInputLogger
-@Inject
-constructor(
- @WifiInputLog val buffer: LogBuffer,
-) {
- fun logOnCapabilitiesChanged(
- network: Network,
- networkCapabilities: NetworkCapabilities,
- isDefaultNetworkCallback: Boolean,
- ) {
- LoggerHelper.logOnCapabilitiesChanged(
- buffer,
- TAG,
- network,
- networkCapabilities,
- isDefaultNetworkCallback,
- )
- }
-
- fun logOnLost(network: Network, isDefaultNetworkCallback: Boolean) {
- LoggerHelper.logOnLost(buffer, TAG, network, isDefaultNetworkCallback)
- }
-
- fun logIntent(intentName: String) {
- buffer.log(TAG, LogLevel.DEBUG, { str1 = intentName }, { "Intent received: $str1" })
- }
-
- fun logActivity(activity: String) {
- buffer.log(TAG, LogLevel.DEBUG, { str1 = activity }, { "Activity: $str1" })
- }
-
- fun logScanResults() = buffer.log(TAG, LogLevel.DEBUG, {}, { "onScanResultsAvailable" })
-}
-
-private const val TAG = "WifiInputLog"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 45078e32108d..46ca6e908cc1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -22,6 +22,7 @@ import static android.os.BatteryManager.EXTRA_CHARGING_STATUS;
import static android.os.BatteryManager.EXTRA_PRESENT;
import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS;
+import static com.android.systemui.Flags.registerBatteryControllerReceiversInCorestartable;
import static com.android.systemui.util.DumpUtilsKt.asIndenting;
import android.annotation.WorkerThread;
@@ -151,7 +152,9 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
@Override
public void init() {
mLogger.logBatteryControllerInit(this, mHasReceivedBattery);
- registerReceiver();
+ if (!registerBatteryControllerReceiversInCorestartable()) {
+ registerReceiver();
+ }
if (!mHasReceivedBattery) {
// Get initial state. Relying on Sticky behavior until API for getting info.
Intent intent = mContext.registerReceiver(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerStartable.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerStartable.java
new file mode 100644
index 000000000000..7f601c837feb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerStartable.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import static com.android.systemui.Flags.registerBatteryControllerReceiversInCorestartable;
+
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.usb.UsbManager;
+import android.os.PowerManager;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
+
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+/** A {@link CoreStartable} responsible for registering the receivers for
+ * {@link BatteryControllerImpl}.
+ */
+@SysUISingleton
+public class BatteryControllerStartable implements CoreStartable {
+
+ private final BatteryController mBatteryController;
+ private final Executor mBackgroundExecutor;
+
+ private static final String ACTION_LEVEL_TEST = "com.android.systemui.BATTERY_LEVEL_TEST";
+
+ protected final BroadcastDispatcher mBroadcastDispatcher;
+ @Inject
+ public BatteryControllerStartable(
+ BatteryController batteryController,
+ BroadcastDispatcher broadcastDispatcher,
+ @Background Executor backgroundExecutor) {
+ mBatteryController = batteryController;
+ mBroadcastDispatcher = broadcastDispatcher;
+ mBackgroundExecutor = backgroundExecutor;
+ }
+
+ private void registerReceiver() {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_BATTERY_CHANGED);
+ filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
+ filter.addAction(ACTION_LEVEL_TEST);
+ filter.addAction(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED);
+ mBroadcastDispatcher.registerReceiver((BroadcastReceiver) mBatteryController, filter);
+ }
+
+ @Override
+ public void start() {
+ if (registerBatteryControllerReceiversInCorestartable()
+ && mBatteryController instanceof BatteryControllerImpl) {
+ mBackgroundExecutor.execute(() -> registerReceiver());
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index 6b237f8e329b..f19fa20bd999 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -27,6 +27,7 @@ import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
import com.android.systemui.volume.panel.component.mediaoutput.shared.model.SessionWithPlayback
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.shared.model.Result
import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -34,6 +35,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
@@ -53,32 +55,40 @@ constructor(
private val uiEventLogger: UiEventLogger,
) {
- private val sessionWithPlayback: StateFlow<SessionWithPlayback?> =
+ private val sessionWithPlayback: StateFlow<Result<SessionWithPlayback?>> =
interactor.defaultActiveMediaSession
.flatMapLatest { session ->
if (session == null) {
- flowOf(null)
+ flowOf(Result.Data<SessionWithPlayback?>(null))
} else {
- mediaDeviceSessionInteractor.playbackState(session).map { playback ->
- playback?.let { SessionWithPlayback(session, it) }
- }
+ mediaDeviceSessionInteractor
+ .playbackState(session)
+ .map { playback ->
+ playback?.let {
+ Result.Data<SessionWithPlayback?>(SessionWithPlayback(session, it))
+ }
+ }
+ .filterNotNull()
}
}
.stateIn(
coroutineScope,
SharingStarted.Eagerly,
- null,
+ Result.Loading(),
)
val connectedDeviceViewModel: StateFlow<ConnectedDeviceViewModel?> =
combine(sessionWithPlayback, interactor.currentConnectedDevice) {
mediaDeviceSession,
currentConnectedDevice ->
+ if (mediaDeviceSession !is Result.Data) {
+ return@combine null
+ }
ConnectedDeviceViewModel(
- if (mediaDeviceSession?.playback?.isActive == true) {
+ if (mediaDeviceSession.data?.playback?.isActive == true) {
context.getString(
R.string.media_output_label_title,
- mediaDeviceSession.session.appLabel
+ mediaDeviceSession.data.session.appLabel
)
} else {
context.getString(R.string.media_output_title_without_playing)
@@ -96,7 +106,10 @@ constructor(
combine(sessionWithPlayback, interactor.currentConnectedDevice) {
mediaDeviceSession,
currentConnectedDevice ->
- if (mediaDeviceSession?.playback?.isActive == true) {
+ if (mediaDeviceSession !is Result.Data) {
+ return@combine null
+ }
+ if (mediaDeviceSession.data?.playback?.isActive == true) {
val icon =
currentConnectedDevice?.icon?.let { Icon.Loaded(it, null) }
?: Icon.Resource(
@@ -130,6 +143,7 @@ constructor(
fun onBarClick(expandable: Expandable) {
uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_MEDIA_OUTPUT_CLICKED)
- actionsInteractor.onBarClick(sessionWithPlayback.value, expandable)
+ val result = sessionWithPlayback.value
+ actionsInteractor.onBarClick((result as? Result.Data)?.data, expandable)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt
new file mode 100644
index 000000000000..ac8092cd6f3a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.panel.component.volume.domain.interactor
+
+import android.media.AudioDeviceInfo
+import android.media.AudioManager
+import com.android.settingslib.volume.data.repository.AudioRepository
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.isTheSameSession
+import com.android.systemui.volume.panel.component.volume.domain.model.SliderType
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combineTransform
+import kotlinx.coroutines.flow.stateIn
+
+/** Provides volume sliders to show in the Volume Panel. */
+@VolumePanelScope
+class AudioSlidersInteractor
+@Inject
+constructor(
+ @VolumePanelScope scope: CoroutineScope,
+ mediaOutputInteractor: MediaOutputInteractor,
+ audioRepository: AudioRepository,
+) {
+
+ val volumePanelSliders: StateFlow<List<SliderType>> =
+ combineTransform(
+ mediaOutputInteractor.activeMediaDeviceSessions,
+ mediaOutputInteractor.defaultActiveMediaSession,
+ audioRepository.communicationDevice,
+ ) { activeSessions, defaultSession, communicationDevice ->
+ coroutineScope {
+ val viewModels = buildList {
+ if (defaultSession?.isTheSameSession(activeSessions.remote) == true) {
+ addSession(activeSessions.remote)
+ addStream(AudioManager.STREAM_MUSIC)
+ } else {
+ addStream(AudioManager.STREAM_MUSIC)
+ addSession(activeSessions.remote)
+ }
+
+ if (communicationDevice?.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO) {
+ addStream(AudioManager.STREAM_BLUETOOTH_SCO)
+ } else {
+ addStream(AudioManager.STREAM_VOICE_CALL)
+ }
+ addStream(AudioManager.STREAM_RING)
+ addStream(AudioManager.STREAM_NOTIFICATION)
+ addStream(AudioManager.STREAM_ALARM)
+ }
+ emit(viewModels)
+ }
+ }
+ .stateIn(scope, SharingStarted.Eagerly, emptyList())
+
+ private fun MutableList<SliderType>.addSession(remoteMediaDeviceSession: MediaDeviceSession?) {
+ if (remoteMediaDeviceSession?.canAdjustVolume == true) {
+ add(SliderType.MediaDeviceCast(remoteMediaDeviceSession))
+ }
+ }
+
+ private fun MutableList<SliderType>.addStream(stream: Int) {
+ add(SliderType.Stream(AudioStream(stream)))
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/model/SliderType.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/model/SliderType.kt
index b97123b29b68..6129ce543e2e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/model/SliderType.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/model/SliderType.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.panel.component.volume.domain.model
import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
/** The type of volume slider that can be shown at the UI. */
sealed interface SliderType {
@@ -25,5 +26,5 @@ sealed interface SliderType {
data class Stream(val stream: AudioStream) : SliderType
/** The represents media device casting volume. */
- data object MediaDeviceCast : SliderType
+ data class MediaDeviceCast(val session: MediaDeviceSession) : SliderType
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index c8cd6fdbea70..ee642a64242d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -53,6 +53,7 @@ constructor(
mapOf(
AudioStream(AudioManager.STREAM_MUSIC) to R.drawable.ic_music_note,
AudioStream(AudioManager.STREAM_VOICE_CALL) to R.drawable.ic_call,
+ AudioStream(AudioManager.STREAM_BLUETOOTH_SCO) to R.drawable.ic_call,
AudioStream(AudioManager.STREAM_RING) to R.drawable.ic_ring_volume,
AudioStream(AudioManager.STREAM_NOTIFICATION) to R.drawable.ic_volume_ringer,
AudioStream(AudioManager.STREAM_ALARM) to R.drawable.ic_volume_alarm,
@@ -61,6 +62,7 @@ constructor(
mapOf(
AudioStream(AudioManager.STREAM_MUSIC) to R.string.stream_music,
AudioStream(AudioManager.STREAM_VOICE_CALL) to R.string.stream_voice_call,
+ AudioStream(AudioManager.STREAM_BLUETOOTH_SCO) to R.string.stream_voice_call,
AudioStream(AudioManager.STREAM_RING) to R.string.stream_ring,
AudioStream(AudioManager.STREAM_NOTIFICATION) to R.string.stream_notification,
AudioStream(AudioManager.STREAM_ALARM) to R.string.stream_alarm,
@@ -78,6 +80,8 @@ constructor(
VolumePanelUiEvent.VOLUME_PANEL_MUSIC_SLIDER_TOUCHED,
AudioStream(AudioManager.STREAM_VOICE_CALL) to
VolumePanelUiEvent.VOLUME_PANEL_VOICE_CALL_SLIDER_TOUCHED,
+ AudioStream(AudioManager.STREAM_BLUETOOTH_SCO) to
+ VolumePanelUiEvent.VOLUME_PANEL_VOICE_CALL_SLIDER_TOUCHED,
AudioStream(AudioManager.STREAM_RING) to
VolumePanelUiEvent.VOLUME_PANEL_RING_SLIDER_TOUCHED,
AudioStream(AudioManager.STREAM_NOTIFICATION) to
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
index 09e56c1a65a3..741f5cf59853 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
@@ -16,12 +16,12 @@
package com.android.systemui.volume.panel.component.volume.ui.viewmodel
-import android.media.AudioManager
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
-import com.android.systemui.volume.panel.component.mediaoutput.shared.model.isTheSameSession
+import com.android.systemui.volume.panel.component.volume.domain.interactor.AudioSlidersInteractor
+import com.android.systemui.volume.panel.component.volume.domain.model.SliderType
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.AudioStreamSliderViewModel
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.CastVolumeSliderViewModel
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel
@@ -33,12 +33,12 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combineTransform
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.launch
/**
@@ -55,28 +55,21 @@ constructor(
private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
private val streamSliderViewModelFactory: AudioStreamSliderViewModel.Factory,
private val castVolumeSliderViewModelFactory: CastVolumeSliderViewModel.Factory,
+ streamsInteractor: AudioSlidersInteractor,
) {
val sliderViewModels: StateFlow<List<SliderViewModel>> =
- combineTransform(
- mediaOutputInteractor.activeMediaDeviceSessions,
- mediaOutputInteractor.defaultActiveMediaSession,
- ) { activeSessions, defaultSession ->
+ streamsInteractor.volumePanelSliders
+ .transformLatest { sliderTypes ->
coroutineScope {
- val viewModels = buildList {
- if (defaultSession?.isTheSameSession(activeSessions.remote) == true) {
- addRemoteViewModelIfNeeded(this, activeSessions.remote)
- addStreamViewModel(this, AudioManager.STREAM_MUSIC)
- } else {
- addStreamViewModel(this, AudioManager.STREAM_MUSIC)
- addRemoteViewModelIfNeeded(this, activeSessions.remote)
+ val viewModels =
+ sliderTypes.map { type ->
+ when (type) {
+ is SliderType.Stream -> createStreamViewModel(type.stream)
+ is SliderType.MediaDeviceCast ->
+ createSessionViewModel(type.session)
+ }
}
-
- addStreamViewModel(this, AudioManager.STREAM_VOICE_CALL)
- addStreamViewModel(this, AudioManager.STREAM_RING)
- addStreamViewModel(this, AudioManager.STREAM_NOTIFICATION)
- addStreamViewModel(this, AudioManager.STREAM_ALARM)
- }
emit(viewModels)
}
}
@@ -98,29 +91,18 @@ constructor(
scope.launch { mutableIsExpanded.emit(isExpanded) }
}
- private fun CoroutineScope.addRemoteViewModelIfNeeded(
- list: MutableList<SliderViewModel>,
- remoteMediaDeviceSession: MediaDeviceSession?
- ) {
- if (remoteMediaDeviceSession?.canAdjustVolume == true) {
- val viewModel =
- castVolumeSliderViewModelFactory.create(
- remoteMediaDeviceSession,
- this,
- )
- list.add(viewModel)
- }
+ private fun CoroutineScope.createSessionViewModel(
+ session: MediaDeviceSession
+ ): CastVolumeSliderViewModel {
+ return castVolumeSliderViewModelFactory.create(session, this)
}
- private fun CoroutineScope.addStreamViewModel(
- list: MutableList<SliderViewModel>,
- stream: Int,
- ) {
- val viewModel =
- streamSliderViewModelFactory.create(
- AudioStreamSliderViewModel.FactoryAudioStreamWrapper(AudioStream(stream)),
- this,
- )
- list.add(viewModel)
+ private fun CoroutineScope.createStreamViewModel(
+ stream: AudioStream,
+ ): AudioStreamSliderViewModel {
+ return streamSliderViewModelFactory.create(
+ AudioStreamSliderViewModel.FactoryAudioStreamWrapper(stream),
+ this,
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/Result.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/Result.kt
new file mode 100644
index 000000000000..8793538aac0d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/model/Result.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.panel.shared.model
+
+/** Models a loadable result */
+sealed interface Result<T> {
+
+ /** The data is still loading */
+ class Loading<T> : Result<T>
+
+ /** The data is loaded successfully */
+ data class Data<T>(val data: T) : Result<T>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index e48b6397457c..263ddc175647 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -43,6 +43,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.CoreStartable;
+import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.WMComponent;
import com.android.systemui.dagger.qualifiers.Main;
@@ -55,6 +56,7 @@ import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.onehanded.OneHanded;
@@ -124,6 +126,8 @@ public final class WMShell implements
private final UserTracker mUserTracker;
private final DisplayTracker mDisplayTracker;
private final NoteTaskInitializer mNoteTaskInitializer;
+ private final CommunalTransitionViewModel mCommunalTransitionViewModel;
+ private final JavaAdapter mJavaAdapter;
private final Executor mSysUiMainExecutor;
// Listeners and callbacks. Note that we prefer member variable over anonymous class here to
@@ -187,6 +191,8 @@ public final class WMShell implements
UserTracker userTracker,
DisplayTracker displayTracker,
NoteTaskInitializer noteTaskInitializer,
+ CommunalTransitionViewModel communalTransitionViewModel,
+ JavaAdapter javaAdapter,
@Main Executor sysUiMainExecutor) {
mContext = context;
mShell = shell;
@@ -205,6 +211,8 @@ public final class WMShell implements
mUserTracker = userTracker;
mDisplayTracker = displayTracker;
mNoteTaskInitializer = noteTaskInitializer;
+ mCommunalTransitionViewModel = communalTransitionViewModel;
+ mJavaAdapter = javaAdapter;
mSysUiMainExecutor = sysUiMainExecutor;
}
@@ -381,6 +389,8 @@ public final class WMShell implements
void initRecentTasks(RecentTasks recentTasks) {
recentTasks.addAnimationStateListener(mSysUiMainExecutor,
mCommandQueue::onRecentsAnimationStateChanged);
+ mJavaAdapter.alwaysCollectFlow(mCommunalTransitionViewModel.getRecentsBackgroundColor(),
+ recentTasks::setTransitionBackgroundColor);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java
index ffedb30a404d..52af8fb62943 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java
@@ -18,11 +18,12 @@ package com.android.keyguard;
import static com.google.common.truth.Truth.assertThat;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.view.View;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt
index f8fdd8d33a57..6512e70c51e1 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt
@@ -16,7 +16,7 @@
package com.android.keyguard
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
index 08c1de168439..303ae9731c96 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
@@ -50,10 +50,11 @@ import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.text.TextUtils;
+import androidx.test.filters.SmallTest;
+
import com.android.keyguard.logging.CarrierTextManagerLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.WakefulnessLifecycle;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index b4a9d40a6caf..e2063d2f8a04 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -30,7 +30,6 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.content.Context;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.util.AttributeSet;
@@ -40,6 +39,8 @@ import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.clocks.ClockController;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
index 93e7602715b1..6228ff837340 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
@@ -27,12 +27,13 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.hardware.biometrics.BiometricSourceType;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.text.Editable;
import android.text.TextWatcher;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
index edb910a3acc2..c566826fdf19 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
@@ -23,12 +23,13 @@ import static org.mockito.Mockito.when;
import android.content.pm.PackageManager;
import android.os.Handler;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.view.View;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardSliceProvider;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
index 6654a6ce816b..a0e80653792d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
@@ -17,7 +17,6 @@ package com.android.keyguard;
import android.graphics.Color;
import android.net.Uri;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.view.LayoutInflater;
@@ -27,10 +26,11 @@ import androidx.slice.SliceProvider;
import androidx.slice.SliceSpecs;
import androidx.slice.builders.ListBuilder;
import androidx.slice.widget.RowContent;
+import androidx.test.filters.SmallTest;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.KeyguardSliceProvider;
+import com.android.systemui.res.R;
import org.junit.Assert;
import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt
index e6b696454d42..ed61ee12849f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt
@@ -1,8 +1,8 @@
package com.android.keyguard
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import org.junit.Assert.assertEquals
import org.junit.Before
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index b2828a41c4b0..0696a4b880d5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -31,11 +31,12 @@ import static org.mockito.Mockito.when;
import android.animation.AnimatorTestRule;
import android.platform.test.annotations.DisableFlags;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
+import androidx.test.filters.SmallTest;
+
import com.android.app.animation.Interpolators;
import com.android.systemui.Flags;
import com.android.systemui.animation.ViewHierarchyAnimator;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt
index 17f77aac8959..3b57d8f85d83 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt
@@ -16,9 +16,9 @@
package com.android.keyguard
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
import com.android.systemui.power.shared.model.ScreenPowerState
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.test.runCurrent
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
index 86439e557f8b..afd2034424ff 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
@@ -1,6 +1,6 @@
package com.android.keyguard
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.LayoutInflater
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index fde45d34a4fd..68b6d9d69c51 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -106,11 +106,12 @@ import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.text.TextUtils;
+import androidx.test.filters.SmallTest;
+
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.internal.foldables.FoldGracePeriodProvider;
import com.android.internal.jank.InteractionJankMonitor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java b/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java
index 532c59aab18b..d6a5b4b11521 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java
@@ -28,8 +28,8 @@ import static org.mockito.Mockito.verify;
import android.content.res.Resources;
import android.graphics.Canvas;
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.settingslib.R;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java
index 7c121e1754bf..d7bd59e43e30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java
@@ -31,36 +31,57 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
-import android.testing.AndroidTestingRunner;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
import androidx.test.filters.SmallTest;
import com.android.settingslib.SliceBroadcastRelay;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-@RunWith(AndroidTestingRunner.class)
+import java.util.List;
+
+@RunWith(Parameterized.class)
@SmallTest
public class SliceBroadcastRelayHandlerTest extends SysuiTestCase {
+ @Parameterized.Parameters(name = "{0}")
+ public static List<FlagsParameterization> getFlags() {
+ return FlagsParameterization.allCombinationsOf(
+ Flags.FLAG_SLICE_BROADCAST_RELAY_IN_BACKGROUND);
+ }
+
private static final String TEST_ACTION = "com.android.systemui.action.TEST_ACTION";
+ private final FakeExecutor mBackgroundExecutor = new FakeExecutor(new FakeSystemClock());
+
private SliceBroadcastRelayHandler mRelayHandler;
private Context mSpyContext;
@Mock
private BroadcastDispatcher mBroadcastDispatcher;
+
+ public SliceBroadcastRelayHandlerTest(FlagsParameterization flags) {
+ mSetFlagsRule.setFlagsParameterization(flags);
+ }
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mSpyContext = spy(mContext);
- mRelayHandler = new SliceBroadcastRelayHandler(mSpyContext, mBroadcastDispatcher);
+ mRelayHandler = new SliceBroadcastRelayHandler(mSpyContext, mBroadcastDispatcher,
+ mBackgroundExecutor);
}
@Test
@@ -80,6 +101,7 @@ public class SliceBroadcastRelayHandlerTest extends SysuiTestCase {
intent.putExtra(SliceBroadcastRelay.EXTRA_URI, testUri);
mRelayHandler.handleIntent(intent);
+ mBackgroundExecutor.runAllReady();
verify(mSpyContext).registerReceiver(any(), eq(value), anyInt());
}
@@ -99,12 +121,14 @@ public class SliceBroadcastRelayHandlerTest extends SysuiTestCase {
intent.putExtra(SliceBroadcastRelay.EXTRA_FILTER, value);
mRelayHandler.handleIntent(intent);
+ mBackgroundExecutor.runAllReady();
ArgumentCaptor<BroadcastReceiver> relay = ArgumentCaptor.forClass(BroadcastReceiver.class);
verify(mSpyContext).registerReceiver(relay.capture(), eq(value), anyInt());
intent = new Intent(SliceBroadcastRelay.ACTION_UNREGISTER);
intent.putExtra(SliceBroadcastRelay.EXTRA_URI, ContentProvider.maybeAddUserId(testUri, 0));
mRelayHandler.handleIntent(intent);
+ mBackgroundExecutor.runAllReady();
verify(mSpyContext).unregisterReceiver(eq(relay.getValue()));
}
@@ -119,6 +143,7 @@ public class SliceBroadcastRelayHandlerTest extends SysuiTestCase {
Intent intent = new Intent(SliceBroadcastRelay.ACTION_UNREGISTER);
intent.putExtra(SliceBroadcastRelay.EXTRA_URI, ContentProvider.maybeAddUserId(testUri, 0));
mRelayHandler.handleIntent(intent);
+ mBackgroundExecutor.runAllReady();
// No crash
}
@@ -138,6 +163,7 @@ public class SliceBroadcastRelayHandlerTest extends SysuiTestCase {
intent.putExtra(SliceBroadcastRelay.EXTRA_FILTER, value);
mRelayHandler.handleIntent(intent);
+ mBackgroundExecutor.runAllReady();
ArgumentCaptor<BroadcastReceiver> relay = ArgumentCaptor.forClass(BroadcastReceiver.class);
verify(mSpyContext).registerReceiver(relay.capture(), eq(value), anyInt());
relay.getValue().onReceive(mSpyContext, new Intent(TEST_ACTION));
@@ -146,8 +172,10 @@ public class SliceBroadcastRelayHandlerTest extends SysuiTestCase {
}
@Test
- public void testRegisteredWithDispatcher() {
+ @DisableFlags(Flags.FLAG_SLICE_BROADCAST_RELAY_IN_BACKGROUND)
+ public void testRegisteredWithDispatcher_onMainThread() {
mRelayHandler.start();
+ mBackgroundExecutor.runAllReady();
verify(mBroadcastDispatcher)
.registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class));
@@ -155,6 +183,19 @@ public class SliceBroadcastRelayHandlerTest extends SysuiTestCase {
.registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class));
}
+ @Test
+ @EnableFlags(Flags.FLAG_SLICE_BROADCAST_RELAY_IN_BACKGROUND)
+ public void testRegisteredWithDispatcher_onBackgroundThread() {
+ mRelayHandler.start();
+ mBackgroundExecutor.runAllReady();
+
+ verify(mBroadcastDispatcher)
+ .registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class),
+ eq(mBackgroundExecutor));
+ verify(mSpyContext, never())
+ .registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class));
+ }
+
public static class Receiver extends BroadcastReceiver {
private static BroadcastReceiver sReceiver;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
index 12f334ba08bb..5bc9aa43e825 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
@@ -16,49 +16,84 @@
package com.android.systemui.accessibility;
+import static android.os.Build.HW_TIMEOUT_MULTIPLIER;
+
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
+import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
import android.window.InputTransferToken;
+import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
@SmallTest
@TestableLooper.RunWithLooper
@RunWith(AndroidTestingRunner.class)
public class FullscreenMagnificationControllerTest extends SysuiTestCase {
-
+ private static final long ANIMATION_DURATION_MS = 100L;
+ private static final long WAIT_TIMEOUT_S = 5L * HW_TIMEOUT_MULTIPLIER;
+ private static final long ANIMATION_TIMEOUT_MS =
+ 5L * ANIMATION_DURATION_MS * HW_TIMEOUT_MULTIPLIER;
private FullscreenMagnificationController mFullscreenMagnificationController;
private SurfaceControlViewHost mSurfaceControlViewHost;
+ private ValueAnimator mShowHideBorderAnimator;
+ private SurfaceControl.Transaction mTransaction;
+ private TestableWindowManager mWindowManager;
@Before
public void setUp() {
getInstrumentation().runOnMainSync(() -> mSurfaceControlViewHost =
- new SurfaceControlViewHost(mContext, mContext.getDisplay(),
- new InputTransferToken(), "FullscreenMagnification"));
-
+ spy(new SurfaceControlViewHost(mContext, mContext.getDisplay(),
+ new InputTransferToken(), "FullscreenMagnification")));
Supplier<SurfaceControlViewHost> scvhSupplier = () -> mSurfaceControlViewHost;
+ final WindowManager wm = mContext.getSystemService(WindowManager.class);
+ mWindowManager = new TestableWindowManager(wm);
+ mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
+ mTransaction = new SurfaceControl.Transaction();
+ mShowHideBorderAnimator = spy(newNullTargetObjectAnimator());
mFullscreenMagnificationController = new FullscreenMagnificationController(
mContext,
+ mContext.getMainExecutor(),
mContext.getSystemService(AccessibilityManager.class),
mContext.getSystemService(WindowManager.class),
- scvhSupplier);
+ scvhSupplier,
+ mTransaction,
+ mShowHideBorderAnimator);
}
@After
@@ -69,29 +104,143 @@ public class FullscreenMagnificationControllerTest extends SysuiTestCase {
}
@Test
- public void onFullscreenMagnificationActivationChange_activated_visibleBorder() {
- getInstrumentation().runOnMainSync(
- () -> mFullscreenMagnificationController
- .onFullscreenMagnificationActivationChanged(true)
- );
-
- // Wait for Rects updated.
- waitForIdleSync();
+ public void enableFullscreenMagnification_visibleBorder() throws InterruptedException {
+ CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
+ CountDownLatch animationEndLatch = new CountDownLatch(1);
+ mTransaction.addTransactionCommittedListener(
+ Runnable::run, transactionCommittedLatch::countDown);
+ mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ animationEndLatch.countDown();
+ }
+ });
+ getInstrumentation().runOnMainSync(() ->
+ //Enable fullscreen magnification
+ mFullscreenMagnificationController
+ .onFullscreenMagnificationActivationChanged(true));
+ assertTrue("Failed to wait for transaction committed",
+ transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
+ assertTrue("Failed to wait for animation to be finished",
+ animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ verify(mShowHideBorderAnimator).start();
assertThat(mSurfaceControlViewHost.getView().isVisibleToUser()).isTrue();
}
@Test
- public void onFullscreenMagnificationActivationChange_deactivated_invisibleBorder() {
- getInstrumentation().runOnMainSync(
- () -> {
- mFullscreenMagnificationController
- .onFullscreenMagnificationActivationChanged(true);
- mFullscreenMagnificationController
- .onFullscreenMagnificationActivationChanged(false);
+ public void disableFullscreenMagnification_reverseAnimationAndReleaseScvh()
+ throws InterruptedException {
+ CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
+ CountDownLatch enableAnimationEndLatch = new CountDownLatch(1);
+ CountDownLatch disableAnimationEndLatch = new CountDownLatch(1);
+ mTransaction.addTransactionCommittedListener(
+ Runnable::run, transactionCommittedLatch::countDown);
+ mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
+ if (isReverse) {
+ disableAnimationEndLatch.countDown();
+ } else {
+ enableAnimationEndLatch.countDown();
}
- );
+ }
+ });
+ getInstrumentation().runOnMainSync(() ->
+ //Enable fullscreen magnification
+ mFullscreenMagnificationController
+ .onFullscreenMagnificationActivationChanged(true));
+ assertTrue("Failed to wait for transaction committed",
+ transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
+ assertTrue("Failed to wait for enabling animation to be finished",
+ enableAnimationEndLatch.await(
+ ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ verify(mShowHideBorderAnimator).start();
+
+ getInstrumentation().runOnMainSync(() ->
+ // Disable fullscreen magnification
+ mFullscreenMagnificationController
+ .onFullscreenMagnificationActivationChanged(false));
- assertThat(mSurfaceControlViewHost.getView()).isNull();
+ assertTrue("Failed to wait for disabling animation to be finished",
+ disableAnimationEndLatch.await(
+ ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ verify(mShowHideBorderAnimator).reverse();
+ verify(mSurfaceControlViewHost).release();
}
+ @Test
+ public void onFullscreenMagnificationActivationChangeTrue_deactivating_reverseAnimator()
+ throws InterruptedException {
+ // Simulate the hiding border animation is running
+ when(mShowHideBorderAnimator.isRunning()).thenReturn(true);
+ CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
+ CountDownLatch animationEndLatch = new CountDownLatch(1);
+ mTransaction.addTransactionCommittedListener(
+ Runnable::run, transactionCommittedLatch::countDown);
+ mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ animationEndLatch.countDown();
+ }
+ });
+
+ getInstrumentation().runOnMainSync(
+ () -> mFullscreenMagnificationController
+ .onFullscreenMagnificationActivationChanged(true));
+
+ assertTrue("Failed to wait for transaction committed",
+ transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
+ assertTrue("Failed to wait for animation to be finished",
+ animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ verify(mShowHideBorderAnimator).reverse();
+ }
+
+ @Test
+ public void onScreenSizeChanged_activated_borderChangedToExpectedSize()
+ throws InterruptedException {
+ CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
+ CountDownLatch animationEndLatch = new CountDownLatch(1);
+ mTransaction.addTransactionCommittedListener(
+ Runnable::run, transactionCommittedLatch::countDown);
+ mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ animationEndLatch.countDown();
+ }
+ });
+ getInstrumentation().runOnMainSync(() ->
+ //Enable fullscreen magnification
+ mFullscreenMagnificationController
+ .onFullscreenMagnificationActivationChanged(true));
+ assertTrue("Failed to wait for transaction committed",
+ transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
+ assertTrue("Failed to wait for animation to be finished",
+ animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ final Rect testWindowBounds = new Rect(
+ mWindowManager.getCurrentWindowMetrics().getBounds());
+ testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
+ testWindowBounds.right + 100, testWindowBounds.bottom + 100);
+ mWindowManager.setWindowBounds(testWindowBounds);
+
+ getInstrumentation().runOnMainSync(() ->
+ mFullscreenMagnificationController.onConfigurationChanged(
+ ActivityInfo.CONFIG_SCREEN_SIZE));
+
+ int borderOffset = mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnifier_border_width_fullscreen_with_offset)
+ - mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnifier_border_width_fullscreen);
+ final int newWidth = testWindowBounds.width() + 2 * borderOffset;
+ final int newHeight = testWindowBounds.height() + 2 * borderOffset;
+ verify(mSurfaceControlViewHost).relayout(newWidth, newHeight);
+ }
+
+ private ValueAnimator newNullTargetObjectAnimator() {
+ final ValueAnimator animator =
+ ObjectAnimator.ofFloat(/* target= */ null, View.ALPHA, 0f, 1f);
+ Interpolator interpolator = new DecelerateInterpolator(2.5f);
+ animator.setInterpolator(interpolator);
+ animator.setDuration(ANIMATION_DURATION_MS);
+ return animator;
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
index 41d5d5d919e2..25e5470e2781 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
@@ -101,7 +101,7 @@ public class IMagnificationConnectionTest extends SysuiTestCase {
}).when(mAccessibilityManager).setMagnificationConnection(
any(IMagnificationConnection.class));
mMagnification = new Magnification(getContext(),
- getContext().getMainThreadHandler(), mCommandQueue,
+ getContext().getMainThreadHandler(), getContext().getMainExecutor(), mCommandQueue,
mModeSwitchesController, mSysUiState, mOverviewProxyService, mSecureSettings,
mDisplayTracker, getContext().getSystemService(DisplayManager.class), mA11yLogger);
mMagnification.mWindowMagnificationControllerSupplier =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
index 3b5cbea079a2..6dc5b7212cd0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
@@ -121,7 +121,8 @@ public class MagnificationTest extends SysuiTestCase {
mCommandQueue = new CommandQueue(getContext(), mDisplayTracker);
mMagnification = new Magnification(getContext(),
- getContext().getMainThreadHandler(), mCommandQueue, mModeSwitchesController,
+ getContext().getMainThreadHandler(), getContext().getMainExecutor(),
+ mCommandQueue, mModeSwitchesController,
mSysUiState, mOverviewProxyService, mSecureSettings, mDisplayTracker,
getContext().getSystemService(DisplayManager.class), mA11yLogger);
mMagnification.mWindowMagnificationControllerSupplier = new FakeControllerSupplier(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java
index b843fda34ced..516b6658d24a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java
@@ -23,11 +23,12 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.Size;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.FakeSharedPreferences;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
index c0e108ef75f8..5e7adb7671f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
@@ -17,9 +17,9 @@
package com.android.systemui.biometrics.domain.interactor
import android.graphics.Rect
-import android.test.suitebuilder.annotation.SmallTest
import android.view.MotionEvent
import android.view.Surface
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt
index f5990be8e0c3..b7ed27f7c71e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt
@@ -21,7 +21,7 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.UserHandle
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import com.android.systemui.SysuiTestCase
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
index eb6e5174d078..2c1718176571 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
@@ -24,7 +24,7 @@ import android.os.Handler
import android.os.Looper
import android.os.PatternMatcher
import android.os.UserHandle
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import com.android.systemui.SysuiTestCase
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
index 39e4467bd84f..582f30110a5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
@@ -21,7 +21,7 @@ import android.content.Context
import android.content.IntentFilter
import android.os.Handler
import android.os.UserHandle
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import com.android.systemui.SysuiTestCase
diff --git a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt
index feaedc53fbc1..1e522fc9941a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt
@@ -17,7 +17,7 @@
package com.android.systemui.camera
import android.content.Intent
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import com.android.systemui.SysuiTestCase
import org.junit.Assert.assertFalse
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
index 6a79ee8553c5..6cc3ef19ef3b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
@@ -16,7 +16,7 @@
package com.android.systemui.controls.ui
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.view.HapticFeedbackConstants
import com.android.systemui.SysuiTestCase
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
index 523127e08a20..dbe59e66b955 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
@@ -23,7 +23,7 @@ import android.content.res.Resources
import android.content.res.Resources.NotFoundException
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_SYSUI_TEAMFOOD
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicReleaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicReleaseTest.kt
index 70d6dd93459e..943e2124f6b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicReleaseTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicReleaseTest.kt
@@ -17,7 +17,7 @@ package com.android.systemui.flags
import android.content.pm.PackageManager.NameNotFoundException
import android.content.res.Resources
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertThrows
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
index 7c1325e2b355..d500dd260267 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
@@ -16,7 +16,7 @@
package com.android.systemui.flags
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
import java.io.PrintWriter
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
index 303aaa128378..5e87a6fd3fa3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
@@ -19,7 +19,7 @@ import android.content.Context
import android.database.ContentObserver
import android.net.Uri
import android.os.Handler
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt
index db6f85f12a42..755cc4615cc2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt
@@ -15,7 +15,7 @@
*/
package com.android.systemui.flags
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
index 7d7abab4a0f5..0fdda082b9c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
@@ -15,7 +15,7 @@
*/
package com.android.systemui.flags
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.statusbar.policy.BatteryController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt
index e287f19b2455..3c965ce61aa2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt
@@ -17,7 +17,7 @@
package com.android.systemui.flags
import android.os.PowerManager
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.util.concurrency.FakeExecutor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
index 1f04828c359a..0116e5326e47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
@@ -15,7 +15,7 @@
*/
package com.android.systemui.flags
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.power.domain.interactor.PowerInteractor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
index 1d1949d12479..2daa86bf5c14 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
@@ -16,8 +16,8 @@
package com.android.systemui.flags
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.DeviceConfigProxyFake
import com.android.systemui.util.concurrency.FakeExecutor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt
index 4ba1bc6dfbbb..8a29217c33c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/fragments/FragmentServiceTest.kt
@@ -2,7 +2,7 @@ package com.android.systemui.fragments
import android.app.Fragment
import android.os.Looper
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.util.mockito.mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt
index eb885fd4ae41..4fcd3bb7600a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt
@@ -18,9 +18,9 @@ package com.android.systemui.media.controls.ui.animation
import android.graphics.drawable.Animatable2
import android.graphics.drawable.Drawable
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt
index 711669eb2dd0..bb95ba356fef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt
@@ -17,9 +17,9 @@
package com.android.systemui.media.controls.ui.animation
import android.animation.Animator
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import junit.framework.Assert.fail
import org.junit.After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt
index 37dea11ccaaf..791563a839b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt
@@ -16,12 +16,13 @@
package com.android.systemui.media.controls.ui.controller
-import android.test.suitebuilder.annotation.SmallTest
+import android.provider.Settings
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.media.controls.ui.view.MediaHost
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt
index b4f5528cb523..4101c94daa2f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt
@@ -16,7 +16,7 @@
package com.android.systemui.notetask
import android.os.UserHandle
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt
index e09c804e4611..2c86a8dd47b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt
@@ -19,7 +19,7 @@ package com.android.systemui.notetask
import android.app.role.RoleManager
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.eq
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
index ebd34de463f4..231b3331ce52 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
@@ -25,8 +25,8 @@ import android.content.pm.PackageManager.ApplicationInfoFlags
import android.hardware.input.InputSettings
import android.os.UserHandle
import android.os.UserManager
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
index 4547bffaccf0..9429725718db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
@@ -42,11 +42,12 @@ import android.os.BatteryManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.UserHandle;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
+import androidx.test.filters.SmallTest;
+
import com.android.internal.logging.UiEventLogger;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.settingslib.fuelgauge.BatterySaverUtils;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index 2bdad2bc49a9..cae170f6f1c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -38,18 +38,19 @@ import android.os.Temperature;
import android.provider.Settings;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.testing.TestableResources;
+import androidx.test.filters.SmallTest;
+
import com.android.settingslib.fuelgauge.Estimate;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.power.PowerUI.WarningsUI;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index 0275643b8489..e50320df2740 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -1,7 +1,7 @@
package com.android.systemui.qs
import android.content.res.Configuration
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableResources
import android.view.ContextThemeWrapper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index 718e302f8236..0abcc64e0dc8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -42,7 +42,6 @@ import android.os.Handler;
import android.os.Looper;
import android.provider.DeviceConfig;
import android.provider.Settings;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -52,6 +51,7 @@ import android.view.View;
import android.widget.TextView;
import androidx.annotation.Nullable;
+import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogTransitionAnimator;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
index 0eae5aae3600..98adbb051337 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
@@ -31,12 +31,12 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.TestableLooper;
import android.view.ContextThemeWrapper;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTest.java
index c43c3e629c8a..29e2a8a8824f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TouchAnimatorTest.java
@@ -16,9 +16,9 @@ package com.android.systemui.qs;
import static junit.framework.Assert.assertEquals;
-import android.test.suitebuilder.annotation.SmallTest;
import android.view.View;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
index 33f8f1fd9087..ef979d2d0cac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -31,7 +31,7 @@ import android.os.Handler
import android.os.Parcel
import android.service.quicksettings.IQSTileService
import android.service.quicksettings.Tile
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.IWindowManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileColorPickerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileColorPickerTest.java
index b8e6403696a5..eb013c5975b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileColorPickerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileColorPickerTest.java
@@ -19,12 +19,12 @@ import static junit.framework.Assert.assertEquals;
import android.content.res.ColorStateList;
import android.service.quicksettings.Tile;
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
import org.junit.Before;
import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
index 81424565daee..0a36ae6a4c57 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
@@ -56,9 +56,9 @@ import android.os.UserHandle;
import android.service.quicksettings.IQSService;
import android.service.quicksettings.IQSTileService;
import android.service.quicksettings.TileService;
-import android.test.suitebuilder.annotation.SmallTest;
import androidx.annotation.Nullable;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
index 28331bbfafb2..0ff29dbbfde7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
@@ -34,8 +34,8 @@ import android.content.IntentFilter;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.UserHandle;
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index d0118218134c..248af1e859f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -34,11 +34,12 @@ import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.quicksettings.IQSTileService;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.qs.QSHost;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
index 512ca5315530..ecbd0f54df5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
@@ -385,7 +385,7 @@ class QSTileViewImplTest : SysuiTestCase() {
}
@Test
- fun onStateChange_longPressEffectActive_withInvalidDuration_doesNotCreateEffect() {
+ fun onStateChange_longPressEffectActive_withInvalidDuration_doesNotInitializeEffect() {
val state = QSTile.State() // A state that handles longPress
// GIVEN an invalid long-press effect duration
@@ -399,7 +399,7 @@ class QSTileViewImplTest : SysuiTestCase() {
}
@Test
- fun onStateChange_longPressEffectActive_withValidDuration_createsEffect() {
+ fun onStateChange_longPressEffectActive_withValidDuration_initializesEffect() {
// GIVEN a test state that handles long-press and a valid long-press effect duration
val state = QSTile.State()
@@ -420,7 +420,7 @@ class QSTileViewImplTest : SysuiTestCase() {
tileView.changeState(state)
// THEN the view binder no longer binds the view to the long-press effect
- assertThat(tileView.longPressEffectHandle).isNull()
+ assertThat(tileView.isLongPressEffectBound).isFalse()
}
@Test
@@ -435,7 +435,7 @@ class QSTileViewImplTest : SysuiTestCase() {
tileView.changeState(state)
// THEN the view is bounded to the long-press effect
- assertThat(tileView.longPressEffectHandle).isNotNull()
+ assertThat(tileView.isLongPressEffectBound).isTrue()
}
@Test
@@ -451,7 +451,7 @@ class QSTileViewImplTest : SysuiTestCase() {
tileView.changeState(state)
// THEN the view binder does not bind the view and no effect is initialized
- assertThat(tileView.longPressEffectHandle).isNull()
+ assertThat(tileView.isLongPressEffectBound).isFalse()
assertThat(tileView.isLongPressEffectInitialized).isFalse()
}
@@ -470,7 +470,7 @@ class QSTileViewImplTest : SysuiTestCase() {
tileView.changeState(state)
// THEN the view binder does not bind the view and no effect is initialized
- assertThat(tileView.longPressEffectHandle).isNull()
+ assertThat(tileView.isLongPressEffectBound).isFalse()
assertThat(tileView.isLongPressEffectInitialized).isFalse()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
index 440270b6ebfa..c02fca7f264b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
@@ -27,13 +27,13 @@ import android.Manifest;
import android.content.pm.PackageManager;
import android.hardware.SensorPrivacyManager;
import android.os.Handler;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableResources;
+import androidx.test.filters.SmallTest;
+
import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.ActivityStarter;
@@ -43,6 +43,7 @@ import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.DeviceStateRotationLockSettingController;
import com.android.systemui.statusbar.policy.RotationLockController;
@@ -57,7 +58,6 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
index c900463c7159..0f3714385725 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -26,6 +26,7 @@ import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import java.lang.IllegalStateException
+import java.util.function.Consumer
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runCurrent
@@ -78,7 +79,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
fun executeScreenshots_severalDisplays_callsControllerForEachOne() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
verify(controllerFactory).create(eq(0), any())
@@ -107,7 +108,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
fun executeScreenshots_providedImageType_callsOnlyDefaultDisplayController() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(
createScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE),
onSaved,
@@ -136,7 +137,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
fun executeScreenshots_onlyVirtualDisplays_noInteractionsWithControllers() =
testScope.runTest {
setDisplays(display(TYPE_VIRTUAL, id = 0), display(TYPE_VIRTUAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
verifyNoMoreInteractions(controllerFactory)
@@ -154,7 +155,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
display(TYPE_OVERLAY, id = 2),
display(TYPE_WIFI, id = 3)
)
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
verify(controller0, times(4)).handleScreenshot(any(), any(), any())
@@ -165,7 +166,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
fun executeScreenshots_reportsOnFinishedOnlyWhenBothFinished() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
val capturer0 = ArgumentCaptor<TakeScreenshotService.RequestCallback>()
@@ -190,7 +191,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
fun executeScreenshots_oneFinishesOtherFails_reportFailsOnlyAtTheEnd() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
val capturer0 = ArgumentCaptor<TakeScreenshotService.RequestCallback>()
@@ -217,7 +218,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
fun executeScreenshots_allDisplaysFail_reportsFail() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
val capturer0 = ArgumentCaptor<TakeScreenshotService.RequestCallback>()
@@ -244,7 +245,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
fun onDestroy_propagatedToControllers() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
screenshotExecutor.onDestroy()
@@ -256,7 +257,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
fun removeWindows_propagatedToControllers() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
screenshotExecutor.removeWindows()
@@ -270,7 +271,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
fun onCloseSystemDialogsReceived_propagatedToControllers() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
screenshotExecutor.onCloseSystemDialogsReceived()
@@ -286,7 +287,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
whenever(controller0.isPendingSharedTransition).thenReturn(true)
whenever(controller1.isPendingSharedTransition).thenReturn(false)
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
screenshotExecutor.onCloseSystemDialogsReceived()
@@ -304,7 +305,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
val toBeReturnedByProcessor = ScreenshotData.forTesting()
requestProcessor.toReturn = toBeReturnedByProcessor
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(screenshotRequest, onSaved, callback)
assertThat(requestProcessor.processed)
@@ -321,7 +322,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
fun executeScreenshots_errorFromProcessor_logsScreenshotRequested() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
requestProcessor.shouldThrowException = true
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
@@ -338,7 +339,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
fun executeScreenshots_errorFromProcessor_logsUiError() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
requestProcessor.shouldThrowException = true
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
@@ -355,7 +356,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
fun executeScreenshots_errorFromProcessorOnDefaultDisplay_showsErrorNotification() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
requestProcessor.shouldThrowException = true
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
@@ -368,7 +369,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
fun executeScreenshots_errorFromProcessorOnSecondaryDisplay_showsErrorNotification() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
requestProcessor.shouldThrowException = true
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
@@ -381,7 +382,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
fun executeScreenshots_errorFromScreenshotController_reportsRequested() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
whenever(controller0.handleScreenshot(any(), any(), any()))
.thenThrow(IllegalStateException::class.java)
whenever(controller1.handleScreenshot(any(), any(), any()))
@@ -401,7 +402,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
fun executeScreenshots_errorFromScreenshotController_reportsError() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
whenever(controller0.handleScreenshot(any(), any(), any()))
.thenThrow(IllegalStateException::class.java)
whenever(controller1.handleScreenshot(any(), any(), any()))
@@ -421,7 +422,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
fun executeScreenshots_errorFromScreenshotController_showsErrorNotification() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- val onSaved = { _: Uri -> }
+ val onSaved = { _: Uri? -> }
whenever(controller0.handleScreenshot(any(), any(), any()))
.thenThrow(IllegalStateException::class.java)
whenever(controller1.handleScreenshot(any(), any(), any()))
@@ -434,6 +435,25 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
screenshotExecutor.onDestroy()
}
+ @Test
+ fun executeScreenshots_finisherCalledWithNullUri_succeeds() =
+ testScope.runTest {
+ setDisplays(display(TYPE_INTERNAL, id = 0))
+ var onSavedCallCount = 0
+ val onSaved: (Uri?) -> Unit = {
+ assertThat(it).isNull()
+ onSavedCallCount += 1
+ }
+ whenever(controller0.handleScreenshot(any(), any(), any())).thenAnswer {
+ (it.getArgument(1) as Consumer<Uri?>).accept(null)
+ }
+
+ screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
+ assertThat(onSavedCallCount).isEqualTo(1)
+
+ screenshotExecutor.onDestroy()
+ }
+
private suspend fun TestScope.setDisplays(vararg displays: Display) {
fakeDisplayRepository.emit(displays.toSet())
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
index 0776aa7d1845..77b5c9115295 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -232,7 +232,7 @@ internal class FakeScreenshotExecutor : TakeScreenshotExecutor {
override fun onCloseSystemDialogsReceived() {}
override suspend fun executeScreenshots(
screenshotRequest: ScreenshotRequest,
- onSaved: (Uri) -> Unit,
+ onSaved: (Uri?) -> Unit,
requestCallback: RequestCallback,
) {
requestReceived = screenshotRequest
@@ -248,7 +248,7 @@ internal class FakeScreenshotExecutor : TakeScreenshotExecutor {
override fun executeScreenshotsAsync(
screenshotRequest: ScreenshotRequest,
- onSaved: Consumer<Uri>,
+ onSaved: Consumer<Uri?>,
requestCallback: RequestCallback,
) {
runBlocking {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt
index 621b0582c538..254f1e1efe13 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt
@@ -188,6 +188,18 @@ object DisplayContentScenarios {
* actual values returned by ActivityTaskManager
*/
object RootTasks {
+ /** An empty RootTaskInfo with no child tasks. */
+ val emptyWithNoChildTasks =
+ newRootTaskInfo(
+ taskId = 2,
+ visible = true,
+ running = true,
+ numActivities = 0,
+ bounds = FULL_SCREEN,
+ ) {
+ emptyList()
+ }
+
/**
* The empty RootTaskInfo that is always at the end of a list from ActivityTaskManager when
* no other visible activities are in split mode
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt
new file mode 100644
index 000000000000..9e3ae054d31b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt
@@ -0,0 +1,225 @@
+/*
+ * 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.screenshot.policy
+
+import android.content.ComponentName
+import android.os.UserHandle
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.screenshot.data.model.DisplayContentModel
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.FILES
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.YOUTUBE
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.YOUTUBE_PIP
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.RootTasks.emptyRootSplit
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.RootTasks.fullScreen
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.RootTasks.launcher
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.TaskSpec
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.pictureInPictureApp
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.singleFullScreen
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.splitScreenApps
+import com.android.systemui.screenshot.data.model.SystemUiState
+import com.android.systemui.screenshot.data.repository.profileTypeRepository
+import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.Matched
+import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.NotMatched
+import com.android.systemui.screenshot.policy.CaptureType.FullScreen
+import com.android.systemui.screenshot.policy.TestUserIds.PERSONAL
+import com.android.systemui.screenshot.policy.TestUserIds.PRIVATE
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+class PrivateProfilePolicyTest {
+ private val kosmos = Kosmos()
+ private val policy = PrivateProfilePolicy(kosmos.profileTypeRepository)
+
+ // TODO:
+ // private app in PIP
+ // private app below personal PIP app
+ // Freeform windows
+
+ @Test
+ fun shadeExpanded_notMatched() = runTest {
+ val result =
+ policy.check(
+ singleFullScreen(
+ spec = TaskSpec(taskId = 1002, name = YOUTUBE, userId = PRIVATE),
+ shadeExpanded = true
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(NotMatched(PrivateProfilePolicy.NAME, PrivateProfilePolicy.SHADE_EXPANDED))
+ }
+
+ @Test
+ fun noPrivate_notMatched() = runTest {
+ val result =
+ policy.check(
+ singleFullScreen(TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL))
+ )
+
+ assertThat(result)
+ .isEqualTo(NotMatched(PrivateProfilePolicy.NAME, PrivateProfilePolicy.NO_VISIBLE_TASKS))
+ }
+
+ @Test
+ fun withPrivateFullScreen_isMatched() = runTest {
+ val result =
+ policy.check(
+ singleFullScreen(TaskSpec(taskId = 1002, name = YOUTUBE, userId = PRIVATE))
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ Matched(
+ PrivateProfilePolicy.NAME,
+ PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
+ CaptureParameters(
+ type = FullScreen(displayId = 0),
+ component = ComponentName.unflattenFromString(YOUTUBE),
+ owner = UserHandle.of(PRIVATE)
+ )
+ )
+ )
+ }
+
+ @Test
+ fun withPrivateNotVisible_notMatched() = runTest {
+ val result =
+ policy.check(
+ DisplayContentModel(
+ displayId = 0,
+ systemUiState = SystemUiState(shadeExpanded = false),
+ rootTasks =
+ listOf(
+ fullScreen(
+ TaskSpec(taskId = 1002, name = FILES, userId = PERSONAL),
+ visible = true
+ ),
+ fullScreen(
+ TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
+ visible = false
+ ),
+ launcher(visible = false),
+ emptyRootSplit,
+ )
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ NotMatched(
+ PrivateProfilePolicy.NAME,
+ PrivateProfilePolicy.NO_VISIBLE_TASKS,
+ )
+ )
+ }
+
+ @Test
+ fun withPrivateFocusedInSplitScreen_isMatched() = runTest {
+ val result =
+ policy.check(
+ splitScreenApps(
+ top = TaskSpec(taskId = 1002, name = FILES, userId = PERSONAL),
+ bottom = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
+ focusedTaskId = 1003
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ Matched(
+ PrivateProfilePolicy.NAME,
+ PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
+ CaptureParameters(
+ type = FullScreen(displayId = 0),
+ component = ComponentName.unflattenFromString(YOUTUBE),
+ owner = UserHandle.of(PRIVATE)
+ )
+ )
+ )
+ }
+
+ @Test
+ fun withPrivateNotFocusedInSplitScreen_isMatched() = runTest {
+ val result =
+ policy.check(
+ splitScreenApps(
+ top = TaskSpec(taskId = 1002, name = FILES, userId = PERSONAL),
+ bottom = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
+ focusedTaskId = 1002
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ Matched(
+ PrivateProfilePolicy.NAME,
+ PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
+ CaptureParameters(
+ type = FullScreen(displayId = 0),
+ component = ComponentName.unflattenFromString(FILES),
+ owner = UserHandle.of(PRIVATE)
+ )
+ )
+ )
+ }
+
+ @Test
+ fun withPrivatePictureInPictureApp_isMatched() = runTest {
+ val result =
+ policy.check(
+ pictureInPictureApp(TaskSpec(taskId = 1002, name = YOUTUBE_PIP, userId = PRIVATE))
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ Matched(
+ PrivateProfilePolicy.NAME,
+ PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
+ CaptureParameters(
+ type = FullScreen(displayId = 0),
+ component = ComponentName.unflattenFromString(YOUTUBE_PIP),
+ owner = UserHandle.of(PRIVATE)
+ )
+ )
+ )
+ }
+
+ @Test
+ fun withPrivateAppBelowPictureInPictureApp_isMatched() = runTest {
+ val result =
+ policy.check(
+ pictureInPictureApp(
+ pip = TaskSpec(taskId = 1002, name = YOUTUBE_PIP, userId = PERSONAL),
+ fullScreen = TaskSpec(taskId = 1003, name = FILES, userId = PRIVATE),
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ Matched(
+ PrivateProfilePolicy.NAME,
+ PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
+ CaptureParameters(
+ type = FullScreen(displayId = 0),
+ component = ComponentName.unflattenFromString(YOUTUBE_PIP),
+ owner = UserHandle.of(PRIVATE)
+ )
+ )
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
new file mode 100644
index 000000000000..5d35528b0cf0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
@@ -0,0 +1,253 @@
+/*
+ * 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.screenshot.policy
+
+import android.content.ComponentName
+import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.screenshot.data.model.DisplayContentModel
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.FILES
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.YOUTUBE
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FREE_FORM
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FULL_SCREEN
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.SPLIT_TOP
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.RootTasks
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.TaskSpec
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.freeFormApps
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.pictureInPictureApp
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.singleFullScreen
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.splitScreenApps
+import com.android.systemui.screenshot.data.model.SystemUiState
+import com.android.systemui.screenshot.data.repository.profileTypeRepository
+import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult
+import com.android.systemui.screenshot.policy.CapturePolicy.PolicyResult.NotMatched
+import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
+import com.android.systemui.screenshot.policy.TestUserIds.PERSONAL
+import com.android.systemui.screenshot.policy.TestUserIds.WORK
+import com.android.systemui.screenshot.policy.WorkProfilePolicy.Companion.DESKTOP_MODE_ENABLED
+import com.android.systemui.screenshot.policy.WorkProfilePolicy.Companion.SHADE_EXPANDED
+import com.android.systemui.screenshot.policy.WorkProfilePolicy.Companion.WORK_TASK_IS_TOP
+import com.android.systemui.screenshot.policy.WorkProfilePolicy.Companion.WORK_TASK_NOT_TOP
+import com.android.window.flags.Flags
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Rule
+import org.junit.Test
+
+class WorkProfilePolicyTest {
+ @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
+ private val kosmos = Kosmos()
+ private val policy = WorkProfilePolicy(kosmos.profileTypeRepository)
+
+ /**
+ * There is no guarantee that every RootTaskInfo contains a non-empty list of child tasks. Test
+ * the case where the RootTaskInfo would match but child tasks are empty.
+ */
+ @Test
+ fun withEmptyChildTasks_notMatched() = runTest {
+ val result =
+ policy.check(
+ DisplayContentModel(
+ displayId = 0,
+ systemUiState = SystemUiState(shadeExpanded = false),
+ rootTasks = listOf(RootTasks.emptyWithNoChildTasks)
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ NotMatched(
+ WorkProfilePolicy.NAME,
+ WORK_TASK_NOT_TOP,
+ )
+ )
+ }
+
+ @Test
+ fun noWorkApp_notMatched() = runTest {
+ val result =
+ policy.check(
+ singleFullScreen(TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL))
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ NotMatched(
+ WorkProfilePolicy.NAME,
+ WORK_TASK_NOT_TOP,
+ )
+ )
+ }
+
+ @Test
+ fun withWorkFullScreen_shadeExpanded_notMatched() = runTest {
+ val result =
+ policy.check(
+ singleFullScreen(
+ TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+ shadeExpanded = true
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ NotMatched(
+ WorkProfilePolicy.NAME,
+ SHADE_EXPANDED,
+ )
+ )
+ }
+
+ @Test
+ fun withWorkFullScreen_matched() = runTest {
+ val result =
+ policy.check(singleFullScreen(TaskSpec(taskId = 1002, name = FILES, userId = WORK)))
+
+ assertThat(result)
+ .isEqualTo(
+ PolicyResult.Matched(
+ policy = WorkProfilePolicy.NAME,
+ reason = WORK_TASK_IS_TOP,
+ CaptureParameters(
+ type = IsolatedTask(taskId = 1002, taskBounds = FULL_SCREEN),
+ component = ComponentName.unflattenFromString(FILES),
+ owner = UserHandle.of(WORK),
+ )
+ )
+ )
+ }
+
+ @Test
+ fun withWorkFocusedInSplitScreen_matched() = runTest {
+ val result =
+ policy.check(
+ splitScreenApps(
+ top = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+ bottom = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
+ focusedTaskId = 1002
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ PolicyResult.Matched(
+ policy = WorkProfilePolicy.NAME,
+ reason = WORK_TASK_IS_TOP,
+ CaptureParameters(
+ type = IsolatedTask(taskId = 1002, taskBounds = SPLIT_TOP),
+ component = ComponentName.unflattenFromString(FILES),
+ owner = UserHandle.of(WORK),
+ )
+ )
+ )
+ }
+
+ @Test
+ fun withWorkNotFocusedInSplitScreen_notMatched() = runTest {
+ val result =
+ policy.check(
+ splitScreenApps(
+ top = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+ bottom = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
+ focusedTaskId = 1003
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ NotMatched(
+ WorkProfilePolicy.NAME,
+ WORK_TASK_NOT_TOP,
+ )
+ )
+ }
+
+ @Test
+ fun withWorkBelowPersonalPictureInPicture_matched() = runTest {
+ val result =
+ policy.check(
+ pictureInPictureApp(
+ pip = TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL),
+ fullScreen = TaskSpec(taskId = 1003, name = FILES, userId = WORK),
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ PolicyResult.Matched(
+ policy = WorkProfilePolicy.NAME,
+ reason = WORK_TASK_IS_TOP,
+ CaptureParameters(
+ type = IsolatedTask(taskId = 1003, taskBounds = FULL_SCREEN),
+ component = ComponentName.unflattenFromString(FILES),
+ owner = UserHandle.of(WORK),
+ )
+ )
+ )
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun withWorkFocusedInFreeForm_matched() = runTest {
+ val result =
+ policy.check(
+ freeFormApps(
+ TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL),
+ TaskSpec(taskId = 1003, name = FILES, userId = WORK),
+ focusedTaskId = 1003
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ PolicyResult.Matched(
+ policy = WorkProfilePolicy.NAME,
+ reason = WORK_TASK_IS_TOP,
+ CaptureParameters(
+ type = IsolatedTask(taskId = 1003, taskBounds = FREE_FORM),
+ component = ComponentName.unflattenFromString(FILES),
+ owner = UserHandle.of(WORK),
+ )
+ )
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun withWorkFocusedInFreeForm_desktopModeEnabled_notMatched() = runTest {
+ val result =
+ policy.check(
+ freeFormApps(
+ TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL),
+ TaskSpec(taskId = 1003, name = FILES, userId = WORK),
+ focusedTaskId = 1003
+ )
+ )
+
+ assertThat(result)
+ .isEqualTo(
+ NotMatched(
+ WorkProfilePolicy.NAME,
+ DESKTOP_MODE_ENABLED,
+ )
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index e611da0044e6..ee03236d00b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -36,6 +36,7 @@ import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.communal.util.CommunalColors
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -85,6 +86,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
@Mock private lateinit var communalViewModel: CommunalViewModel
@Mock private lateinit var powerManager: PowerManager
@Mock private lateinit var dialogFactory: SystemUIDialogFactory
+ @Mock private lateinit var communalColors: CommunalColors
private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
private lateinit var shadeInteractor: ShadeInteractor
private lateinit var keyguardInteractor: KeyguardInteractor
@@ -116,6 +118,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
keyguardInteractor,
shadeInteractor,
powerManager,
+ communalColors,
kosmos.sceneDataSourceDelegator,
)
testableLooper = TestableLooper.get(this)
@@ -156,6 +159,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
keyguardInteractor,
shadeInteractor,
powerManager,
+ communalColors,
kosmos.sceneDataSourceDelegator,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 0a8e470f8a7c..7a39a0d01534 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -154,6 +154,7 @@ import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinatorLogger;
import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardViewStateRepository;
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor;
import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.AmbientState;
@@ -161,6 +162,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationListContain
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator;
+import com.android.systemui.statusbar.notification.stack.data.repository.FakeHeadsUpNotificationRepository;
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
@@ -358,6 +360,10 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
protected TestScope mTestScope = mKosmos.getTestScope();
protected ShadeInteractor mShadeInteractor;
protected PowerInteractor mPowerInteractor;
+ protected FakeHeadsUpNotificationRepository mFakeHeadsUpNotificationRepository =
+ new FakeHeadsUpNotificationRepository();
+ protected HeadsUpNotificationInteractor mHeadsUpNotificationInteractor =
+ new HeadsUpNotificationInteractor(mFakeHeadsUpNotificationRepository);
protected NotificationPanelViewController.TouchHandler mTouchHandler;
protected ConfigurationController mConfigurationController;
protected SysuiStatusBarStateController mStatusBarStateController;
@@ -384,7 +390,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mFeatureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false);
mFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false);
mFeatureFlags.set(Flags.QS_USER_DETAIL_SHORTCUT, false);
@@ -730,6 +735,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mActivityStarter,
mSharedNotificationContainerInteractor,
mActiveNotificationsInteractor,
+ mHeadsUpNotificationInteractor,
mShadeAnimationInteractor,
mKeyguardViewConfigurator,
mDeviceEntryFaceAuthInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 650c45bf83ea..81e20c17a8ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -46,6 +46,7 @@ import android.animation.Animator;
import android.animation.ValueAnimator;
import android.graphics.Point;
import android.os.PowerManager;
+import android.platform.test.annotations.DisableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.MotionEvent;
@@ -62,6 +63,7 @@ import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm;
@@ -1287,6 +1289,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo
}
@Test
+ @DisableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
public void shadeExpanded_whenHunIsPresent() {
when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
index 4df7ef533c7a..6631d29da719 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
@@ -14,8 +14,11 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.shade
+import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.HapticFeedbackConstants
@@ -29,10 +32,14 @@ import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarState.KEYGUARD
import com.android.systemui.statusbar.StatusBarState.SHADE
import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
+import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
+import com.android.systemui.statusbar.notification.stack.data.repository.setNotifications
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.advanceUntilIdle
@@ -235,4 +242,41 @@ class NotificationPanelViewControllerWithCoroutinesTest :
val bottomAreaAlpha by collectLastValue(mFakeKeyguardRepository.bottomAreaAlpha)
assertThat(bottomAreaAlpha).isEqualTo(1f)
}
+
+ @Test
+ @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ fun shadeExpanded_whenHunIsPresent() = runTest {
+ launch(mainDispatcher) {
+ givenViewAttached()
+
+ // WHEN a pinned heads up is present
+ mFakeHeadsUpNotificationRepository.setNotifications(
+ fakeHeadsUpRowRepository("key", isPinned = true)
+ )
+ }
+ advanceUntilIdle()
+
+ // THEN the panel should be visible
+ assertThat(mNotificationPanelViewController.isExpanded).isTrue()
+ }
+
+ @Test
+ @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ fun shadeExpanded_whenHunIsAnimatingAway() = runTest {
+ launch(mainDispatcher) {
+ givenViewAttached()
+
+ // WHEN a heads up is animating away
+ mFakeHeadsUpNotificationRepository.isHeadsUpAnimatingAway.value = true
+ }
+ advanceUntilIdle()
+
+ // THEN the panel should be visible
+ assertThat(mNotificationPanelViewController.isExpanded).isTrue()
+ }
+
+ private fun fakeHeadsUpRowRepository(key: String, isPinned: Boolean = false) =
+ FakeHeadsUpRowRepository(key = key, elementKey = Any()).apply {
+ this.isPinned.value = isPinned
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 8c5a4d0bbcae..da09579e1bde 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -38,8 +38,6 @@ import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
import com.android.systemui.flags.Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION
-import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON
-import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_FEATURES
import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -166,8 +164,6 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
.thenReturn(emptyFlow<TransitionStep>())
featureFlagsClassic = FakeFeatureFlagsClassic()
- featureFlagsClassic.set(TRACKPAD_GESTURE_COMMON, true)
- featureFlagsClassic.set(TRACKPAD_GESTURE_FEATURES, false)
featureFlagsClassic.set(SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
featureFlagsClassic.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index ba8eb6f4ba36..f380b6c700cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -155,8 +155,6 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
.thenReturn(emptyFlow())
val featureFlags = FakeFeatureFlags()
- featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true)
- featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false)
featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
mSetFlagsRule.disableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt
index 8841f481695d..3cb48d9b2462 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt
@@ -15,7 +15,7 @@
package com.android.systemui.shared.animation
import android.graphics.Point
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.view.Display
import android.view.Surface.ROTATION_0
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
index c39b29fb4435..e9222c3ef6ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
@@ -37,8 +37,8 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
index 02954b805ad1..7ddf7a31ad49 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
@@ -26,9 +26,9 @@ import static junit.framework.Assert.fail;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
-import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
index 16eb1d9dbca1..b18b7f8f8129 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.commandline
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
index 5bc75e8b84c1..7e88ae080178 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.connectivity
import android.os.UserManager
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.lifecycle.Lifecycle
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java
index 44e3bb43c4ce..7bd77a62ffcd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java
@@ -22,14 +22,14 @@ import static org.mockito.Matchers.eq;
import android.os.HandlerThread;
import android.telephony.SubscriptionInfo;
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.connectivity.NetworkController.EmergencyListener;
import com.android.systemui.res.R;
+import com.android.systemui.statusbar.connectivity.NetworkController.EmergencyListener;
import org.junit.Before;
import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt
index a226ded06111..7aed4f7e250b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt
@@ -15,8 +15,8 @@
*/
package com.android.systemui.statusbar.connectivity
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.SysuiTestCase
import junit.framework.Assert.assertFalse
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
index f667b831e440..461d80412cb5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
@@ -39,11 +39,12 @@ import android.os.Looper;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
+import androidx.test.filters.SmallTest;
+
import com.android.settingslib.SignalIcon.MobileIconGroup;
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.settingslib.net.DataUsageController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java
index f6f939ad2e12..3bbf06dbc69c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java
@@ -19,10 +19,11 @@ package com.android.systemui.statusbar.connectivity;
import static junit.framework.Assert.assertEquals;
import android.net.NetworkCapabilities;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
+import androidx.test.filters.SmallTest;
+
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
index 375ca0639926..35609a5faf00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
@@ -33,17 +33,18 @@ import android.telephony.SignalStrength;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
+import androidx.test.filters.SmallTest;
+
import com.android.settingslib.graph.SignalDrawable;
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.settingslib.net.DataUsageController;
-import com.android.systemui.res.R;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.log.LogBuffer;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.util.CarrierConfigTracker;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt
index 9e73487972e8..5bf0a94935cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt
@@ -16,8 +16,8 @@
package com.android.systemui.statusbar.connectivity
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
index 2b9456160122..6b2ee76a75ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
@@ -19,12 +19,13 @@ package com.android.systemui.statusbar.notification;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.widget.FrameLayout;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
index fda8f519758c..fc4702c209e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
@@ -42,10 +42,11 @@ import android.os.Handler;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import androidx.test.filters.SmallTest;
+
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
index 0e24ed49fea0..eafa78e93597 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
@@ -25,13 +25,14 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.ArrayMap;
import android.view.LayoutInflater;
import android.view.View;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.SysuiTestCase;
import com.android.systemui.media.dialog.MediaOutputDialogManager;
import com.android.systemui.res.R;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
index a6381d13f7da..5b72ca07edbe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
@@ -25,10 +25,11 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -38,7 +39,6 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
-
@SmallTest
@org.junit.runner.RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
index 2ef4374ce13a..2d8e69280d30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
@@ -18,8 +18,8 @@ package com.android.systemui.statusbar.notification;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -30,7 +30,6 @@ import static org.mockito.Mockito.verify;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.UiThreadTest;
import android.util.FloatProperty;
@@ -38,6 +37,8 @@ import android.util.Property;
import android.view.View;
import android.view.animation.Interpolator;
+import androidx.test.filters.SmallTest;
+
import com.android.app.animation.Interpolators;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.res.R;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
index 9c20e541e35f..ffb8646942b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
@@ -45,7 +45,6 @@ import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.UiThreadTest;
import android.view.LayoutInflater;
@@ -53,6 +52,8 @@ import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.test.filters.SmallTest;
+
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index 3c1f559e9124..97cb11e2f107 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -65,7 +65,6 @@ import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.LayoutInflater;
@@ -73,11 +72,13 @@ import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.test.filters.SmallTest;
+
import com.android.internal.logging.MetricsLogger;
import com.android.settingslib.notification.ConversationIconFactory;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
+import com.android.systemui.res.R;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.SbnBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index f31b1c4fdc29..13ced928175e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -55,20 +55,20 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.telecom.TelecomManager;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import android.testing.UiThreadTest;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.test.filters.SmallTest;
+
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.Dependency;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -82,8 +82,6 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-import java.util.HashSet;
-import java.util.Set;
import java.util.concurrent.CountDownLatch;
@SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
index 0a15f0de0bf4..4a91cd239d87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
@@ -16,16 +16,23 @@
package com.android.systemui.statusbar.notification.row;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.mock;
+
import android.provider.Settings;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableResources;
import android.testing.UiThreadTest;
import android.util.KeyValueListParser;
-import com.android.systemui.res.R;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
+import com.android.systemui.res.R;
import org.junit.Before;
import org.junit.Test;
@@ -33,12 +40,6 @@ import org.junit.runner.RunWith;
import java.util.ArrayList;
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertTrue;
-import static org.mockito.Matchers.isNull;
-import static org.mockito.Mockito.mock;
-
@SmallTest
@RunWith(AndroidTestingRunner.class)
@UiThreadTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
index ccedd364ef67..51665d987888 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
@@ -41,7 +41,6 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.text.SpannableString;
@@ -50,10 +49,12 @@ import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.test.filters.SmallTest;
+
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.Dependency;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -65,8 +66,6 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-import java.util.HashSet;
-import java.util.Set;
import java.util.concurrent.CountDownLatch;
@SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 912ecb340c3c..3a427f3d2975 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -22,6 +22,8 @@ import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -36,8 +38,6 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
-
import android.metrics.LogMaker;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
@@ -56,8 +56,6 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
@@ -126,7 +124,6 @@ import javax.inject.Provider;
public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
- private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
@Mock private NotificationGutsManager mNotificationGutsManager;
@Mock private NotificationsController mNotificationsController;
@Mock private NotificationVisibilityProvider mVisibilityProvider;
@@ -193,8 +190,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
allowTestableLooperAsMainThread();
MockitoAnnotations.initMocks(this);
- mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, true);
-
when(mNotificationSwipeHelperBuilder.build()).thenReturn(mNotificationSwipeHelper);
when(mKeyguardTransitionRepo.getTransitions()).thenReturn(emptyFlow());
}
@@ -299,36 +294,11 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Test
@DisableFlags(FooterViewRefactor.FLAG_NAME)
- public void testUpdateEmptyShadeView_bouncerShowing_flagOff_hideEmptyView() {
+ public void testUpdateEmptyShadeView_bouncerShowing_hideEmptyView() {
when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
initController(/* viewIsAttached= */ true);
- // WHEN the flag is off and *only* CentralSurfaces has bouncer as showing
- mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, false);
- mController.setBouncerShowingFromCentralSurfaces(true);
- when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(false);
-
- setupShowEmptyShadeViewState(true);
- reset(mNotificationStackScrollLayout);
- mController.updateShowEmptyShadeView();
-
- // THEN the CentralSurfaces value is used. Since the bouncer is showing, we hide the empty
- // view.
- verify(mNotificationStackScrollLayout).updateEmptyShadeView(
- /* visible= */ false,
- /* areNotificationsHiddenInShade= */ false);
- }
-
- @Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
- public void testUpdateEmptyShadeView_bouncerShowing_flagOn_hideEmptyView() {
- when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
- initController(/* viewIsAttached= */ true);
-
- // WHEN the flag is on and *only* PrimaryBouncerInteractor has bouncer as showing
- mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, true);
when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(true);
- mController.setBouncerShowingFromCentralSurfaces(false);
setupShowEmptyShadeViewState(true);
reset(mNotificationStackScrollLayout);
@@ -343,36 +313,11 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Test
@DisableFlags(FooterViewRefactor.FLAG_NAME)
- public void testUpdateEmptyShadeView_bouncerNotShowing_flagOff_showEmptyView() {
- when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
- initController(/* viewIsAttached= */ true);
-
- // WHEN the flag is off and *only* CentralSurfaces has bouncer as not showing
- mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, false);
- mController.setBouncerShowingFromCentralSurfaces(false);
- when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(true);
-
- setupShowEmptyShadeViewState(true);
- reset(mNotificationStackScrollLayout);
- mController.updateShowEmptyShadeView();
-
- // THEN the CentralSurfaces value is used. Since the bouncer isn't showing, we can show the
- // empty view.
- verify(mNotificationStackScrollLayout).updateEmptyShadeView(
- /* visible= */ true,
- /* areNotificationsHiddenInShade= */ false);
- }
-
- @Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
- public void testUpdateEmptyShadeView_bouncerNotShowing_flagOn_showEmptyView() {
+ public void testUpdateEmptyShadeView_bouncerNotShowing_showEmptyView() {
when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
initController(/* viewIsAttached= */ true);
- // WHEN the flag is on and *only* PrimaryBouncerInteractor has bouncer as not showing
- mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, true);
when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(false);
- mController.setBouncerShowingFromCentralSurfaces(true);
setupShowEmptyShadeViewState(true);
reset(mNotificationStackScrollLayout);
@@ -1018,7 +963,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mStackLogger,
mLogger,
mNotificationStackSizeCalculator,
- mFeatureFlags,
mNotificationTargetsHelper,
mSecureSettings,
mock(NotificationDismissibilityProvider.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 1e058cac8001..89ae9f4e3547 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -90,6 +90,7 @@ import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefac
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -106,6 +107,7 @@ import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
+import java.util.function.Consumer;
/**
* Tests for {@link NotificationStackScrollLayout}.
@@ -1044,6 +1046,96 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
assertFalse(mStackScroller.getIsBeingDragged());
}
+
+ @Test
+ @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ public void testGenerateHeadsUpDisappearEvent_setsHeadsUpAnimatingAway() {
+ // GIVEN NSSL is ready for HUN animations
+ Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
+
+ // WHEN we generate a disappear event
+ mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false);
+
+ // THEN headsUpAnimatingAway is true
+ verify(headsUpAnimatingAwayListener).accept(true);
+ assertTrue(mStackScroller.mHeadsUpAnimatingAway);
+ }
+
+ @Test
+ @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ public void testGenerateHeadsUpDisappearEvent_stackExpanded_headsUpAnimatingAwayNotSet() {
+ // GIVEN NSSL would be ready for HUN animations, BUT it is expanded
+ Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ assertTrue("Should be expanded by default.", mStackScroller.isExpanded());
+ mStackScroller.setHeadsUpAnimatingAwayListener(headsUpAnimatingAwayListener);
+ mStackScroller.setAnimationsEnabled(true);
+ mStackScroller.setHeadsUpGoingAwayAnimationsAllowed(true);
+
+ // WHEN we generate a disappear event
+ mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false);
+
+ // THEN nothing happens
+ verify(headsUpAnimatingAwayListener, never()).accept(anyBoolean());
+ assertFalse(mStackScroller.mHeadsUpAnimatingAway);
+ }
+
+ @Test
+ @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ public void testGenerateHeadsUpDisappearEvent_pendingAppearEvent_headsUpAnimatingAwayNotSet() {
+ // GIVEN NSSL is ready for HUN animations
+ Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
+ // BUT there is a pending appear event
+ mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ true);
+
+ // WHEN we generate a disappear event
+ mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false);
+
+ // THEN nothing happens
+ verify(headsUpAnimatingAwayListener, never()).accept(anyBoolean());
+ assertFalse(mStackScroller.mHeadsUpAnimatingAway);
+ }
+
+ @Test
+ @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ public void testGenerateHeadsUpAppearEvent_headsUpAnimatingAwayNotSet() {
+ // GIVEN NSSL is ready for HUN animations
+ Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
+
+ // WHEN we generate a disappear event
+ mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ true);
+
+ // THEN headsUpAnimatingWay is not set
+ verify(headsUpAnimatingAwayListener, never()).accept(anyBoolean());
+ assertFalse(mStackScroller.mHeadsUpAnimatingAway);
+ }
+
+ @Test
+ @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
+ public void testOnChildAnimationsFinished_resetsheadsUpAnimatingAway() {
+ // GIVEN NSSL is ready for HUN animations
+ Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener);
+
+ // AND there is a HUN animating away
+ mStackScroller.generateHeadsUpAnimation(row, /* isHeadsUp = */ false);
+ assertTrue("a HUN should be animating away", mStackScroller.mHeadsUpAnimatingAway);
+
+ // WHEN the child animations are finished
+ mStackScroller.onChildAnimationFinished();
+
+ // THEN headsUpAnimatingAway is false
+ verify(headsUpAnimatingAwayListener).accept(false);
+ assertFalse(mStackScroller.mHeadsUpAnimatingAway);
+ }
+
private MotionEvent captureTouchSentToSceneFramework() {
ArgumentCaptor<MotionEvent> captor = ArgumentCaptor.forClass(MotionEvent.class);
verify(mStackScrollLayoutController).sendTouchToSceneFramework(captor.capture());
@@ -1056,6 +1148,14 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
mStackScroller.setStatusBarState(state);
}
+ private void prepareStackScrollerForHunAnimations(
+ Consumer<Boolean> headsUpAnimatingAwayListener) {
+ mStackScroller.setHeadsUpAnimatingAwayListener(headsUpAnimatingAwayListener);
+ mStackScroller.setIsExpanded(false);
+ mStackScroller.setAnimationsEnabled(true);
+ mStackScroller.setHeadsUpGoingAwayAnimationsAllowed(true);
+ }
+
private ExpandableNotificationRow createClearableRow() {
ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
NotificationEntry entry = mock(NotificationEntry.class);
@@ -1116,4 +1216,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
assertThat(mActual.getY()).isEqualTo(expected.getY());
}
}
+
+ private abstract static class BooleanConsumer implements Consumer<Boolean> { }
}
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 e9ec3236a06c..f49dc9895da5 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
@@ -37,12 +37,13 @@ import android.hardware.biometrics.BiometricSourceType;
import android.os.Handler;
import android.os.PowerManager;
import android.os.UserHandle;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.testing.TestableResources;
import android.view.ViewRootImpl;
+import androidx.test.filters.SmallTest;
+
import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.LatencyTracker;
import com.android.keyguard.KeyguardUpdateMonitor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index 6fecbb08f040..7cb41f119c9a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -34,8 +34,8 @@ import android.hardware.display.AmbientDisplayConfiguration;
import android.os.Handler;
import android.os.PowerManager;
import android.provider.Settings;
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.keyguard.KeyguardUpdateMonitor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
index 6150253b917e..4dd97bc90546 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
@@ -17,9 +17,9 @@
package com.android.systemui.statusbar.phone
import android.content.pm.PackageManager
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FakeFeatureFlags
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 b7a3b300a460..b5525b1ce8e9 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
@@ -26,6 +26,7 @@ import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.vcn.VcnTransportInfo
import android.net.wifi.WifiInfo
+import android.net.wifi.WifiManager
import android.os.ParcelUuid
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionInfo
@@ -46,8 +47,10 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.log.LogBuffer
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
@@ -64,10 +67,14 @@ import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRep
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
+import com.android.wifitrackerlib.MergedCarrierEntry
+import com.android.wifitrackerlib.WifiEntry
+import com.android.wifitrackerlib.WifiPickerTracker
import com.google.common.truth.Truth.assertThat
import java.util.UUID
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -97,7 +104,11 @@ import org.mockito.MockitoAnnotations
class MobileConnectionsRepositoryTest : SysuiTestCase() {
private val flags =
- FakeFeatureFlagsClassic().also { it.set(Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO, true) }
+ FakeFeatureFlagsClassic().also {
+ it.set(Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO, true)
+ it.set(Flags.INSTANT_TETHER, true)
+ it.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+ }
private lateinit var connectionFactory: MobileConnectionRepositoryImpl.Factory
private lateinit var carrierMergedFactory: CarrierMergedConnectionRepository.Factory
@@ -114,9 +125,17 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
@Mock private lateinit var summaryLogger: TableLogBuffer
@Mock private lateinit var logBufferFactory: TableLogBufferFactory
@Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var wifiManager: WifiManager
+ @Mock private lateinit var wifiPickerTrackerFactory: WifiPickerTrackerFactory
+ @Mock private lateinit var wifiPickerTracker: WifiPickerTracker
+ @Mock private lateinit var wifiTableLogBuffer: TableLogBuffer
private val mobileMappings = FakeMobileMappingsProxy()
private val subscriptionManagerProxy = FakeSubscriptionManagerProxy()
+ private val mainExecutor = FakeExecutor(FakeSystemClock())
+ private val wifiLogBuffer = LogBuffer("wifi", maxSize = 100, logcatEchoTracker = mock())
+ private val wifiPickerTrackerCallback =
+ argumentCaptor<WifiPickerTracker.WifiPickerTrackerCallback>()
private val dispatcher = StandardTestDispatcher()
private val testScope = TestScope(dispatcher)
@@ -139,6 +158,9 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
mock<TableLogBuffer>()
}
+ whenever(wifiPickerTrackerFactory.create(any(), capture(wifiPickerTrackerCallback), any()))
+ .thenReturn(wifiPickerTracker)
+
// For convenience, set up the subscription info callbacks
whenever(subscriptionManager.getActiveSubscriptionInfo(anyInt())).thenAnswer { invocation ->
when (invocation.getArgument(0) as Int) {
@@ -165,15 +187,14 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
wifiRepository =
WifiRepositoryImpl(
- fakeBroadcastDispatcher,
- connectivityManager,
- connectivityRepository,
- mock(),
- mock(),
- FakeExecutor(FakeSystemClock()),
- dispatcher,
+ flags,
testScope.backgroundScope,
- mock(),
+ mainExecutor,
+ dispatcher,
+ wifiPickerTrackerFactory,
+ wifiManager,
+ wifiLogBuffer,
+ wifiTableLogBuffer,
)
carrierConfigRepository =
@@ -278,7 +299,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
testScope.runTest {
val latest by collectLastValue(underTest.subscriptions)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_CM))
getSubscriptionCallback().onSubscriptionsChanged()
@@ -291,7 +312,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
testScope.runTest {
val latest by collectLastValue(underTest.subscriptions)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_2, SUB_CM))
getSubscriptionCallback().onSubscriptionsChanged()
@@ -445,7 +466,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
collectLastValue(underTest.subscriptions)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_CM))
getSubscriptionCallback().onSubscriptionsChanged()
@@ -462,7 +483,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
collectLastValue(underTest.subscriptions)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_CM))
getSubscriptionCallback().onSubscriptionsChanged()
@@ -479,7 +500,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
collectLastValue(underTest.subscriptions)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_CM))
getSubscriptionCallback().onSubscriptionsChanged()
@@ -491,7 +512,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
// WHEN the wifi network updates to be not carrier merged
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
+ setWifiState(isCarrierMerged = false)
runCurrent()
// THEN the repos update
@@ -507,7 +528,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
collectLastValue(underTest.subscriptions)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
+ setWifiState(isCarrierMerged = false)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_CM))
getSubscriptionCallback().onSubscriptionsChanged()
@@ -520,7 +541,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
// WHEN the wifi network updates to be carrier merged
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
runCurrent()
// THEN the repos update
@@ -562,7 +583,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
collectLastValue(underTest.subscriptions)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_2, SUB_CM))
getSubscriptionCallback().onSubscriptionsChanged()
@@ -845,7 +866,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
val latest by collectLastValue(underTest.hasCarrierMergedConnection)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+ setWifiState(isCarrierMerged = true)
assertThat(latest).isTrue()
}
@@ -867,7 +888,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
val latest by collectLastValue(underTest.hasCarrierMergedConnection)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+ setWifiState(isCarrierMerged = true)
assertThat(latest).isTrue()
}
@@ -890,7 +911,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
val latest by collectLastValue(underTest.hasCarrierMergedConnection)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+ setWifiState(isCarrierMerged = true)
assertThat(latest).isTrue()
}
@@ -912,7 +933,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
val latest by collectLastValue(underTest.hasCarrierMergedConnection)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+ setWifiState(isCarrierMerged = true)
assertThat(latest).isTrue()
}
@@ -946,7 +967,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+ setWifiState(isCarrierMerged = true)
// THEN there's a carrier merged connection
assertThat(latest).isTrue()
@@ -982,7 +1003,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+ setWifiState(isCarrierMerged = true)
// THEN there's a carrier merged connection
assertThat(latest).isTrue()
@@ -1005,7 +1026,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
// BUT the wifi repo has gotten updates that it *is* carrier merged
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
// THEN hasCarrierMergedConnection is true
assertThat(latest).isTrue()
@@ -1026,7 +1047,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
// BUT the wifi repo has gotten updates that it *is* carrier merged
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
// THEN hasCarrierMergedConnection is **false** (The default network being CELLULAR
// takes precedence over the wifi network being carrier merged.)
@@ -1048,7 +1069,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
// BUT the wifi repo has gotten updates that it *is* carrier merged
- getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
// AND we're in airplane mode
airplaneModeRepository.setIsAirplaneMode(true)
@@ -1277,12 +1298,26 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
return callbackCaptor.value!!
}
- // Note: This is used to update the [WifiRepository].
- private fun TestScope.getNormalNetworkCallback(): ConnectivityManager.NetworkCallback {
- runCurrent()
- val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
- verify(connectivityManager).registerNetworkCallback(any(), callbackCaptor.capture())
- return callbackCaptor.value!!
+ private fun setWifiState(isCarrierMerged: Boolean) {
+ if (isCarrierMerged) {
+ val mergedEntry =
+ mock<MergedCarrierEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.isDefaultNetwork).thenReturn(true)
+ whenever(this.subscriptionId).thenReturn(SUB_CM_ID)
+ }
+ whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
+ } else {
+ val wifiEntry =
+ mock<WifiEntry>().apply {
+ whenever(this.isPrimaryNetwork).thenReturn(true)
+ whenever(this.isDefaultNetwork).thenReturn(true)
+ }
+ whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+ whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(null)
+ }
+ wifiPickerTrackerCallback.value.onWifiEntriesChanged()
}
private fun TestScope.getSubscriptionCallback():
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index 31263627213d..f8d50f5e1ac2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -68,14 +68,14 @@ import org.mockito.Mockito.verify
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-class WifiRepositoryViaTrackerLibTest : SysuiTestCase() {
+class WifiRepositoryImplTest : SysuiTestCase() {
// Using lazy means that the class will only be constructed once it's fetched. Because the
// repository internally sets some values on construction, we need to set up some test
// parameters (like feature flags) *before* construction. Using lazy allows us to do that setup
// inside each test case without needing to manually recreate the repository.
- private val underTest: WifiRepositoryViaTrackerLib by lazy {
- WifiRepositoryViaTrackerLib(
+ private val underTest: WifiRepositoryImpl by lazy {
+ WifiRepositoryImpl(
featureFlags,
testScope.backgroundScope,
executor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerStartableTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerStartableTest.java
new file mode 100644
index 000000000000..f1dbee2ff8df
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerStartableTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.settingslib.fuelgauge.BatterySaverUtils;
+import com.android.systemui.Flags;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.power.EnhancedEstimates;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class BatteryControllerStartableTest extends SysuiTestCase {
+
+ private BatteryController mBatteryController;
+ private BatteryControllerStartable mBatteryControllerStartable;
+ private MockitoSession mMockitoSession;
+ private FakeExecutor mExecutor;
+ @Mock
+ private BroadcastDispatcher mBroadcastDispatcher;
+
+ @Before
+ public void setUp() throws IllegalStateException {
+ MockitoAnnotations.initMocks(this);
+ mMockitoSession = mockitoSession()
+ .initMocks(this)
+ .mockStatic(BatterySaverUtils.class)
+ .startMocking();
+
+ mExecutor = new FakeExecutor(new FakeSystemClock());
+
+ mBatteryController = new BatteryControllerImpl(getContext(),
+ mock(EnhancedEstimates.class),
+ mock(PowerManager.class),
+ mock(BroadcastDispatcher.class),
+ mock(DemoModeController.class),
+ mock(DumpManager.class),
+ mock(BatteryControllerLogger.class),
+ new Handler(Looper.getMainLooper()),
+ new Handler(Looper.getMainLooper()));
+ mBatteryController.init();
+
+ mBatteryControllerStartable = new BatteryControllerStartable(mBatteryController,
+ mBroadcastDispatcher, mExecutor);
+ }
+
+ @After
+ public void tearDown() {
+ mMockitoSession.finishMocking();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_REGISTER_BATTERY_CONTROLLER_RECEIVERS_IN_CORESTARTABLE)
+ public void start_flagEnabled_registersListeners() {
+ mBatteryControllerStartable.start();
+ mExecutor.runAllReady();
+
+ verify(mBroadcastDispatcher).registerReceiver(any(), any());
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_REGISTER_BATTERY_CONTROLLER_RECEIVERS_IN_CORESTARTABLE)
+ public void start_flagDisabled_doesNotRegistersListeners() {
+ mBatteryControllerStartable.start();
+ mExecutor.runAllReady();
+
+ verifyZeroInteractions(mBroadcastDispatcher);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index a5c766d82d00..9d4f1fc04594 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -36,11 +36,12 @@ import android.os.BatteryManager;
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerSaveState;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
+import androidx.test.filters.SmallTest;
+
import com.android.dx.mockito.inline.extended.StaticInOrder;
import com.android.settingslib.fuelgauge.BatterySaverUtils;
import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
index 777fa2871a64..1c54263cb0ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
@@ -20,7 +20,7 @@ import android.content.pm.PackageManager
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraManager
import android.hardware.camera2.impl.CameraMetadataNative
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastSender
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
index cb6ce68aaf80..9bb760760cd7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
@@ -44,8 +44,8 @@ import android.net.NetworkRequest;
import android.os.Handler;
import android.os.UserManager;
import android.security.IKeyChainService;
-import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
index dc0d07cab92d..b9557d2461ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
@@ -23,14 +23,15 @@ import static junit.framework.Assert.assertTrue;
import android.app.RemoteInput;
import android.os.Handler;
import android.provider.DeviceConfig;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableResources;
+import androidx.test.filters.SmallTest;
+
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
import com.android.systemui.util.DeviceConfigProxyFake;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffectTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffectTest.kt
new file mode 100644
index 000000000000..b1df159cdefc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/revealeffect/RippleRevealEffectTest.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.surfaceeffects.revealeffect
+
+import android.graphics.RenderEffect
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.animation.AnimatorTestRule
+import com.android.systemui.model.SysUiStateTest
+import com.android.systemui.surfaceeffects.RenderEffectDrawCallback
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class RippleRevealEffectTest : SysUiStateTest() {
+
+ @get:Rule val animatorTestRule = AnimatorTestRule(this)
+
+ @Test
+ fun play_triggersDrawCallback() {
+ var effectFromCallback: RenderEffect? = null
+ val revealEffectConfig = RippleRevealEffectConfig(duration = 1000f)
+ val drawCallback =
+ object : RenderEffectDrawCallback {
+ override fun onDraw(renderEffect: RenderEffect) {
+ effectFromCallback = renderEffect
+ }
+ }
+ val revealEffect = RippleRevealEffect(revealEffectConfig, drawCallback)
+ assertThat(effectFromCallback).isNull()
+
+ revealEffect.play()
+
+ animatorTestRule.advanceTimeBy(500L)
+
+ assertThat(effectFromCallback).isNotNull()
+ }
+
+ @Test
+ fun play_triggersStateChangedCallback() {
+ val revealEffectConfig = RippleRevealEffectConfig(duration = 1000f)
+ val drawCallback =
+ object : RenderEffectDrawCallback {
+ override fun onDraw(renderEffect: RenderEffect) {}
+ }
+ var animationStartedCalled = false
+ var animationEndedCalled = false
+ val stateChangedCallback =
+ object : RippleRevealEffect.AnimationStateChangedCallback {
+ override fun onAnimationStart() {
+ animationStartedCalled = true
+ }
+
+ override fun onAnimationEnd() {
+ animationEndedCalled = true
+ }
+ }
+ val revealEffect =
+ RippleRevealEffect(revealEffectConfig, drawCallback, stateChangedCallback)
+
+ assertThat(animationStartedCalled).isFalse()
+ assertThat(animationEndedCalled).isFalse()
+
+ revealEffect.play()
+
+ assertThat(animationStartedCalled).isTrue()
+
+ animatorTestRule.advanceTimeBy(revealEffectConfig.duration.toLong())
+
+ assertThat(animationEndedCalled).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt
index a85ae7df546b..35f9c417abd3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt
@@ -3,7 +3,7 @@ package com.android.systemui.user
import android.content.pm.UserInfo
import android.graphics.Bitmap
import android.os.UserManager
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import com.android.systemui.SysuiTestCase
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/NotificationChannelsTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/NotificationChannelsTest.java
index 900d7928fb44..243672555512 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/NotificationChannelsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/NotificationChannelsTest.java
@@ -22,9 +22,9 @@ import static org.mockito.Mockito.verify;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
-import android.test.suitebuilder.annotation.SmallTest;
import android.util.ArraySet;
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java
index b10f16c963ed..ab52c34ed3ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java
@@ -25,9 +25,10 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.hardware.Sensor;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
+import androidx.test.filters.SmallTest;
+
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.concurrency.FakeExecution;
import com.android.systemui.util.concurrency.FakeExecutor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
index b0bd83e1799f..741b2e26e2aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
@@ -27,10 +27,11 @@ import static org.mockito.Mockito.verify;
import android.app.Notification;
import android.app.NotificationManager;
import android.media.AudioManager;
-import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import androidx.test.filters.SmallTest;
+
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.concurrency.FakeExecutor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
deleted file mode 100644
index d2c8aea5988f..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2020 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.wmshell;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.verify;
-
-import android.test.suitebuilder.annotation.SmallTest;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.keyguard.ScreenLifecycle;
-import com.android.systemui.keyguard.WakefulnessLifecycle;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.notetask.NoteTaskInitializer;
-import com.android.systemui.settings.FakeDisplayTracker;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.desktopmode.DesktopMode;
-import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
-import com.android.wm.shell.onehanded.OneHanded;
-import com.android.wm.shell.onehanded.OneHandedEventCallback;
-import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
-import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.recents.RecentTasks;
-import com.android.wm.shell.splitscreen.SplitScreen;
-import com.android.wm.shell.sysui.ShellInterface;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Optional;
-import java.util.concurrent.Executor;
-
-/**
- * Tests for {@link WMShell}.
- *
- * Build/Install/Run:
- * atest SystemUITests:WMShellTest
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class WMShellTest extends SysuiTestCase {
- WMShell mWMShell;
-
- @Mock ShellInterface mShellInterface;
- @Mock CommandQueue mCommandQueue;
- @Mock ConfigurationController mConfigurationController;
- @Mock KeyguardStateController mKeyguardStateController;
- @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock ScreenLifecycle mScreenLifecycle;
- @Mock SysUiState mSysUiState;
- @Mock Pip mPip;
- @Mock SplitScreen mSplitScreen;
- @Mock OneHanded mOneHanded;
- @Mock WakefulnessLifecycle mWakefulnessLifecycle;
- @Mock UserTracker mUserTracker;
- @Mock ShellExecutor mSysUiMainExecutor;
- @Mock NoteTaskInitializer mNoteTaskInitializer;
- @Mock DesktopMode mDesktopMode;
- @Mock RecentTasks mRecentTasks;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- FakeDisplayTracker displayTracker = new FakeDisplayTracker(mContext);
- mWMShell = new WMShell(
- mContext,
- mShellInterface,
- Optional.of(mPip),
- Optional.of(mSplitScreen),
- Optional.of(mOneHanded),
- Optional.of(mDesktopMode),
- Optional.of(mRecentTasks),
- mCommandQueue,
- mConfigurationController,
- mKeyguardStateController,
- mKeyguardUpdateMonitor,
- mScreenLifecycle,
- mSysUiState,
- mWakefulnessLifecycle,
- mUserTracker,
- displayTracker,
- mNoteTaskInitializer,
- mSysUiMainExecutor
- );
- }
-
- @Test
- public void initPip_registersCommandQueueCallback() {
- mWMShell.initPip(mPip);
-
- verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks.class));
- }
-
- @Test
- public void initOneHanded_registersCallbacks() {
- mWMShell.initOneHanded(mOneHanded);
-
- verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks.class));
- verify(mScreenLifecycle).addObserver(any(ScreenLifecycle.Observer.class));
- verify(mOneHanded).registerTransitionCallback(any(OneHandedTransitionCallback.class));
- verify(mOneHanded).registerEventCallback(any(OneHandedEventCallback.class));
- }
-
- @Test
- public void initDesktopMode_registersListener() {
- mWMShell.initDesktopMode(mDesktopMode);
- verify(mDesktopMode).addVisibleTasksListener(
- any(DesktopModeTaskRepository.VisibleTasksListener.class),
- any(Executor.class));
- }
-
- @Test
- public void initRecentTasks_registersListener() {
- mWMShell.initRecentTasks(mRecentTasks);
- verify(mRecentTasks).addAnimationStateListener(any(Executor.class), any());
- }
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt
index e36ddc17e5a8..e3c218df2c53 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.communal.ui.viewmodel
import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.util.communalColors
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.viewmodel.dreamingToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToDreamingTransitionViewModel
@@ -37,5 +38,6 @@ val Kosmos.communalTransitionViewModel by
glanceableHubToDreamTransitionViewModel = glanceableHubToDreamingTransitionViewModel,
communalInteractor = communalInteractor,
keyguardTransitionInteractor = keyguardTransitionInteractor,
+ communalColors = communalColors,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibTableLog.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/CommunalColorsKosmos.kt
index 7ca70308361d..e76cf68af05b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTrackerLibTableLog.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/CommunalColorsKosmos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -14,12 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.dagger
+package com.android.systemui.communal.util
-import javax.inject.Qualifier
+import com.android.systemui.kosmos.Kosmos
-/** Wifi logs from [WifiRepositoryViaTrackerLib] in table format. */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class WifiTrackerLibTableLog
+val Kosmos.communalColors: CommunalColors by Kosmos.Fixture { fakeCommunalColors }
+val Kosmos.fakeCommunalColors by Kosmos.Fixture { FakeCommunalColors() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/FakeCommunalColors.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/FakeCommunalColors.kt
new file mode 100644
index 000000000000..7046658728a0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/FakeCommunalColors.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.communal.util
+
+import android.graphics.Color
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeCommunalColors : CommunalColors {
+ private val _backgroundColor = MutableStateFlow(Color.valueOf(Color.BLACK))
+
+ override val backgroundColor: StateFlow<Color>
+ get() = _backgroundColor.asStateFlow()
+
+ fun setBackgroundColor(color: Color) {
+ _backgroundColor.value = color
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
index 636d509663a2..24603ef200d9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
@@ -19,7 +19,6 @@ package com.android.systemui.haptics.qs
import com.android.systemui.haptics.vibratorHelper
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testScope
val Kosmos.qsLongPressEffect by
- Kosmos.Fixture { QSLongPressEffect(vibratorHelper, keyguardInteractor, testScope) }
+ Kosmos.Fixture { QSLongPressEffect(vibratorHelper, keyguardInteractor) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
index 1e25f7fd470e..30a4f21b67f5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
@@ -22,6 +22,7 @@ import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor
val Kosmos.lockscreenContentViewModel by
Kosmos.Fixture {
@@ -32,5 +33,6 @@ val Kosmos.lockscreenContentViewModel by
longPress = keyguardLongPressViewModel,
shadeInteractor = shadeInteractor,
applicationScope = applicationCoroutineScope,
+ unfoldTransitionInteractor = unfoldTransitionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
index dc1b9feea88f..7bf77e5199fe 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
@@ -30,4 +30,7 @@ class FakeHeadsUpNotificationRepository : HeadsUpRepository {
override val topHeadsUpRow: Flow<HeadsUpRowRepository?> = MutableStateFlow(null)
override val activeHeadsUpRows: MutableStateFlow<Set<HeadsUpRowRepository>> =
MutableStateFlow(emptySet())
+ override fun setHeadsUpAnimatingAway(animatingAway: Boolean) {
+ isHeadsUpAnimatingAway.value = animatingAway
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
index c65d0a33cf67..94f6ecd36c7c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+import com.android.systemui.dump.dumpManager
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -48,5 +49,6 @@ val Kosmos.notificationListViewModel by Fixture {
userSetupInteractor,
zenModeInteractor,
testDispatcher,
+ dumpManager,
)
}
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 8ab2e0fa6379..5846c924ad64 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -91,6 +91,16 @@ flag {
}
flag {
+ name: "focus_click_point_window_bounds_from_a11y_window_info"
+ namespace: "accessibility"
+ description: "Uses A11yWindowInfo bounds for focus click point bounds checking"
+ bug: "317166487"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "fullscreen_fling_gesture"
namespace: "accessibility"
description: "When true, adds a fling gesture animation for fullscreen magnification"
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index ccf9a90b5964..cadbd5ef9de4 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -4623,6 +4623,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
android.Manifest.permission.STATUS_BAR_SERVICE);
getMagnificationConnectionManager().setConnection(connection);
+
+ if (com.android.window.flags.Flags.alwaysDrawMagnificationFullscreenBorder()
+ && connection == null
+ && mMagnificationController.isFullScreenMagnificationControllerInitialized()) {
+ // Since the connection does not exist, the system ui cannot provide the border
+ // implementation for fullscreen magnification. So we call reset to deactivate the
+ // fullscreen magnification to prevent the magnified but no border situation.
+ final ArrayList<Display> displays = getValidDisplayList();
+ for (int i = 0; i < displays.size(); i++) {
+ final Display display = displays.get(i);
+ getMagnificationController().getFullScreenMagnificationController()
+ .reset(display.getDisplayId(), false);
+ }
+ }
}
/**
@@ -5234,7 +5248,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
//Clip to the window bounds.
Rect windowBounds = mTempRect1;
- getWindowBounds(focus.getWindowId(), windowBounds);
+ if (Flags.focusClickPointWindowBoundsFromA11yWindowInfo()) {
+ AccessibilityWindowInfo window = focus.getWindow();
+ if (window != null) {
+ window.getBoundsInScreen(windowBounds);
+ }
+ } else {
+ getWindowBounds(focus.getWindowId(), windowBounds);
+ }
if (!boundsInScreenBeforeMagnification.intersect(windowBounds)) {
return false;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
index b119d7d117cd..853b824218f2 100644
--- a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
+++ b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
@@ -17,6 +17,7 @@
package com.android.server.accessibility;
import android.accessibilityservice.AccessibilityService;
+import android.annotation.RequiresNoPermission;
import android.os.Binder;
import android.os.RemoteException;
import android.util.Slog;
@@ -34,7 +35,6 @@ import java.util.List;
* If we are stripping and/or replacing the actions from a window, we need to intercept the
* nodes heading back to the service and swap out the actions.
*/
-@SuppressWarnings("MissingPermissionAnnotation")
public class ActionReplacingCallback extends IAccessibilityInteractionConnectionCallback.Stub {
private static final boolean DEBUG = false;
private static final String LOG_TAG = "ActionReplacingCallback";
@@ -97,6 +97,7 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection
}
@Override
+ @RequiresNoPermission
public void setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info, int interactionId) {
synchronized (mLock) {
if (interactionId == mInteractionId) {
@@ -114,6 +115,7 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection
}
@Override
+ @RequiresNoPermission
public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos,
int interactionId) {
synchronized (mLock) {
@@ -132,6 +134,7 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection
}
@Override
+ @RequiresNoPermission
public void setPrefetchAccessibilityNodeInfoResult(List<AccessibilityNodeInfo> infos,
int interactionId)
throws RemoteException {
@@ -163,6 +166,7 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection
}
@Override
+ @RequiresNoPermission
public void setPerformAccessibilityActionResult(boolean succeeded, int interactionId)
throws RemoteException {
// There's no reason to use this class when performing actions. Do something reasonable.
@@ -170,6 +174,7 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection
}
@Override
+ @RequiresNoPermission
public void sendTakeScreenshotOfWindowError(int errorCode, int interactionId)
throws RemoteException {
mServiceCallback.sendTakeScreenshotOfWindowError(errorCode, interactionId);
@@ -285,6 +290,7 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection
}
@Override
+ @RequiresNoPermission
public void sendAttachOverlayResult(
@AccessibilityService.AttachOverlayResult int result, int interactionId)
throws RemoteException {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index a5bbc7efcb23..aa0af7e5075a 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -118,6 +118,7 @@ public class FullScreenMagnificationController implements
private final MagnificationThumbnailFeatureFlag mMagnificationThumbnailFeatureFlag;
@NonNull private final Supplier<MagnificationThumbnail> mThumbnailSupplier;
+ @NonNull private final Supplier<Boolean> mMagnificationConnectionStateSupplier;
/**
* This class implements {@link WindowManagerInternal.MagnificationCallbacks} and holds
@@ -682,6 +683,12 @@ public class FullScreenMagnificationController implements
if (!mRegistered) {
return false;
}
+ // If the border implementation is on system ui side but the connection is not
+ // established, the fullscreen magnification should not work.
+ if (com.android.window.flags.Flags.alwaysDrawMagnificationFullscreenBorder()
+ && !mMagnificationConnectionStateSupplier.get()) {
+ return false;
+ }
if (DEBUG) {
Slog.i(LOG_TAG,
"setScaleAndCenterLocked(scale = " + scale + ", centerX = " + centerX
@@ -941,7 +948,8 @@ public class FullScreenMagnificationController implements
@NonNull AccessibilityTraceManager traceManager, @NonNull Object lock,
@NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback,
@NonNull MagnificationScaleProvider scaleProvider,
- @NonNull Executor backgroundExecutor) {
+ @NonNull Executor backgroundExecutor,
+ @NonNull Supplier<Boolean> magnificationConnectionStateSupplier) {
this(
new ControllerContext(
context,
@@ -955,7 +963,8 @@ public class FullScreenMagnificationController implements
/* thumbnailSupplier= */ null,
backgroundExecutor,
() -> new Scroller(context),
- TimeAnimator::new);
+ TimeAnimator::new,
+ magnificationConnectionStateSupplier);
}
/** Constructor for tests */
@@ -968,11 +977,13 @@ public class FullScreenMagnificationController implements
Supplier<MagnificationThumbnail> thumbnailSupplier,
@NonNull Executor backgroundExecutor,
Supplier<Scroller> scrollerSupplier,
- Supplier<TimeAnimator> timeAnimatorSupplier) {
+ Supplier<TimeAnimator> timeAnimatorSupplier,
+ @NonNull Supplier<Boolean> magnificationConnectionStateSupplier) {
mControllerCtx = ctx;
mLock = lock;
mScrollerSupplier = scrollerSupplier;
mTimeAnimatorSupplier = timeAnimatorSupplier;
+ mMagnificationConnectionStateSupplier = magnificationConnectionStateSupplier;
mMainThreadId = mControllerCtx.getContext().getMainLooper().getThread().getId();
mScreenStateObserver = new ScreenStateObserver(mControllerCtx.getContext(), this);
addInfoChangedCallback(magnificationInfoChangedCallback);
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 20bec59445c3..76367a2b11c3 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -798,7 +798,9 @@ public class MagnificationController implements MagnificationConnectionManager.C
mLock,
this,
mScaleProvider,
- mBackgroundExecutor
+ mBackgroundExecutor,
+ () -> (isMagnificationConnectionManagerInitialized()
+ && getMagnificationConnectionManager().isConnected())
);
}
}
@@ -831,6 +833,12 @@ public class MagnificationController implements MagnificationConnectionManager.C
}
}
+ private boolean isMagnificationConnectionManagerInitialized() {
+ synchronized (mLock) {
+ return mMagnificationConnectionManager != null;
+ }
+ }
+
private @Nullable PointF getCurrentMagnificationCenterLocked(int displayId, int targetMode) {
if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) {
if (mMagnificationConnectionManager == null
diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig
index 70ecc055929c..590a1efaf576 100644
--- a/services/autofill/bugfixes.aconfig
+++ b/services/autofill/bugfixes.aconfig
@@ -43,8 +43,15 @@ flag {
}
flag {
- name: "include_last_focused_id_and_session_id_in_client_state"
+ name: "add_last_focused_id_to_client_state"
namespace: "autofill"
- description: "Include the current view id and session id into the FillEventHistory as part of ClientState"
+ description: "Include the current view id into the FillEventHistory events as part of ClientState"
bug: "334141398"
}
+
+flag {
+ name: "add_session_id_to_client_state"
+ namespace: "autofill"
+ description: "Include the session id into the FillEventHistory events as part of ClientState"
+ bug: "333927465"
+}
diff --git a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
index 82e9a26310e8..7a2106bb7753 100644
--- a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
+++ b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
@@ -175,7 +175,7 @@ class BackupRestoreProcessor {
// Create a new association reassigned to this user and a valid association ID
final String packageName = restored.getPackageName();
- final int newId = mAssociationStore.getNextId(userId);
+ final int newId = mAssociationStore.getNextId();
AssociationInfo newAssociation = new AssociationInfo.Builder(newId, userId, packageName,
restored).build();
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 9cfb5351f6cf..c892b84ab4d5 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -85,7 +85,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
final int userId = getNextIntArgRequired();
final List<AssociationInfo> associationsForUser =
mAssociationStore.getActiveAssociationsByUser(userId);
- final int maxId = mAssociationStore.getMaxId(userId);
+ final int maxId = mAssociationStore.getMaxId();
out.println("Max ID: " + maxId);
out.println("Association ID | Package Name | Mac Address");
for (AssociationInfo association : associationsForUser) {
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index 15ad4cc4c7fc..d09d7e672f9d 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -284,7 +284,7 @@ public class AssociationRequestsProcessor {
@Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice,
boolean selfManaged, @Nullable IAssociationRequestCallback callback,
@Nullable ResultReceiver resultReceiver) {
- final int id = mAssociationStore.getNextId(userId);
+ final int id = mAssociationStore.getNextId();
final long timestamp = System.currentTimeMillis();
final AssociationInfo association = new AssociationInfo(id, userId, packageName,
diff --git a/services/companion/java/com/android/server/companion/association/AssociationStore.java b/services/companion/java/com/android/server/companion/association/AssociationStore.java
index ae2b70852a35..29e8095f8680 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationStore.java
@@ -132,7 +132,7 @@ public class AssociationStore {
@GuardedBy("mLock")
private final Map<Integer, AssociationInfo> mIdToAssociationMap = new HashMap<>();
@GuardedBy("mLock")
- private final Map<Integer, Integer> mUserToMaxId = new HashMap<>();
+ private int mMaxId = 0;
@GuardedBy("mLocalListeners")
private final Set<OnChangeListener> mLocalListeners = new LinkedHashSet<>();
@@ -162,7 +162,7 @@ public class AssociationStore {
mPersisted = false;
mIdToAssociationMap.clear();
- mUserToMaxId.clear();
+ mMaxId = 0;
// The data is stored in DE directories, so we can read the data for all users now
// (which would not be possible if the data was stored to CE directories).
@@ -172,7 +172,7 @@ public class AssociationStore {
for (AssociationInfo association : entry.getValue().getAssociations()) {
mIdToAssociationMap.put(association.getId(), association);
}
- mUserToMaxId.put(entry.getKey(), entry.getValue().getMaxId());
+ mMaxId = Math.max(mMaxId, entry.getValue().getMaxId());
}
mPersisted = true;
@@ -183,18 +183,18 @@ public class AssociationStore {
/**
* Get the current max association id.
*/
- public int getMaxId(int userId) {
+ public int getMaxId() {
synchronized (mLock) {
- return mUserToMaxId.getOrDefault(userId, 0);
+ return mMaxId;
}
}
/**
* Get the next available association id.
*/
- public int getNextId(int userId) {
+ public int getNextId() {
synchronized (mLock) {
- return getMaxId(userId) + 1;
+ return getMaxId() + 1;
}
}
@@ -214,7 +214,7 @@ public class AssociationStore {
}
mIdToAssociationMap.put(id, association);
- mUserToMaxId.put(userId, Math.max(mUserToMaxId.getOrDefault(userId, 0), id));
+ mMaxId = Math.max(mMaxId, id);
writeCacheToDisk(userId);
@@ -305,7 +305,7 @@ public class AssociationStore {
mExecutor.execute(() -> {
Associations associations = new Associations();
synchronized (mLock) {
- associations.setMaxId(mUserToMaxId.getOrDefault(userId, 0));
+ associations.setMaxId(mMaxId);
associations.setAssociations(
CollectionUtils.filter(mIdToAssociationMap.values().stream().toList(),
a -> a.getUserId() == userId));
diff --git a/packages/CrashRecovery/services/java/com/android/server/ExplicitHealthCheckController.java b/services/core/java/com/android/server/ExplicitHealthCheckController.java
index 3d610d3747c9..3d610d3747c9 100644
--- a/packages/CrashRecovery/services/java/com/android/server/ExplicitHealthCheckController.java
+++ b/services/core/java/com/android/server/ExplicitHealthCheckController.java
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index bdc4a7afae89..2545620a2630 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -22,6 +22,7 @@ per-file *AppOp* = file:/core/java/android/permission/OWNERS
per-file *Battery* = file:/BATTERY_STATS_OWNERS
per-file *BinaryTransparency* = file:/core/java/android/transparency/OWNERS
per-file *Binder* = file:/core/java/com/android/internal/os/BINDER_OWNERS
+per-file ExplicitHealthCheckController.java = file:/services/core/java/com/android/server/crashrecovery/OWNERS
per-file *Gnss* = file:/services/core/java/com/android/server/location/OWNERS
per-file **IpSec* = file:/services/core/java/com/android/server/net/OWNERS
per-file **IpSec* = file:/services/core/java/com/android/server/vcn/OWNERS
@@ -35,9 +36,9 @@ per-file DynamicSystemService.java = file:/packages/DynamicSystemInstallationSer
per-file GestureLauncherService.java = file:platform/packages/apps/EmergencyInfo:/OWNERS
per-file MmsServiceBroker.java = file:/telephony/OWNERS
per-file NetIdManager.java = file:/services/core/java/com/android/server/net/OWNERS
-per-file PackageWatchdog.java, RescueParty.java = file:/services/core/java/com/android/server/rollback/OWNERS
+per-file PackageWatchdog.java = file:/services/core/java/com/android/server/crashrecovery/OWNERS
per-file PinnerService.java = file:/core/java/android/app/pinner/OWNERS
-per-file RescueParty.java = shuc@google.com, ancr@google.com, harshitmahajan@google.com
+per-file RescueParty.java = file:/services/core/java/com/android/server/crashrecovery/OWNERS
per-file SensitiveContentProtectionManagerService.java = file:/core/java/android/permission/OWNERS
per-file SystemClockTime.java = file:/services/core/java/com/android/server/timedetector/OWNERS
per-file SystemTimeZone.java = file:/services/core/java/com/android/server/timezonedetector/OWNERS
diff --git a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 75a8bdfe5416..6f20adf74ee2 100644
--- a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -39,15 +39,15 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
+import android.util.LongArrayQueue;
import android.util.Slog;
import android.util.Xml;
-import android.utils.BackgroundThread;
-import android.utils.LongArrayQueue;
-import android.utils.XmlUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index f86eb61c365f..271d552fc574 100644
--- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -31,6 +31,7 @@ import android.content.pm.VersionedPackage;
import android.crashrecovery.flags.Flags;
import android.os.Build;
import android.os.Environment;
+import android.os.FileUtils;
import android.os.PowerManager;
import android.os.RecoverySystem;
import android.os.SystemClock;
@@ -43,11 +44,10 @@ import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
-import android.utils.ArrayUtils;
-import android.utils.FileUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import com.android.server.PackageWatchdog.FailureReasons;
import com.android.server.PackageWatchdog.PackageHealthObserver;
import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
index 1e7bc397825c..6c7546eca50b 100644
--- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
+++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
@@ -16,6 +16,7 @@
package com.android.server;
+import static android.permission.flags.Flags.sensitiveContentImprovements;
import static android.permission.flags.Flags.sensitiveNotificationAppProtection;
import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS;
import static android.view.flags.Flags.sensitiveContentAppProtection;
@@ -24,6 +25,7 @@ import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDI
import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS;
import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START;
import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP;
+import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_NOTIFICATION_APP_PROTECTION_SESSION;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -91,9 +93,11 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
private boolean mProjectionActive = false;
private static class MediaProjectionSession {
- final int mUid;
- final long mSessionId;
- final boolean mIsExempted;
+ private final int mUid;
+ private final long mSessionId;
+ private final boolean mIsExempted;
+ private final ArraySet<String> mAllSeenNotificationKeys = new ArraySet<>();
+ private final ArraySet<String> mSeenOtpNotificationKeys = new ArraySet<>();
MediaProjectionSession(int uid, boolean isExempted, long sessionId) {
mUid = uid;
@@ -123,6 +127,14 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
);
}
+ public void logAppNotificationsProtected() {
+ FrameworkStatsLog.write(
+ SENSITIVE_NOTIFICATION_APP_PROTECTION_SESSION,
+ mSessionId,
+ mAllSeenNotificationKeys.size(),
+ mSeenOtpNotificationKeys.size());
+ }
+
public void logAppBlocked(int uid) {
FrameworkStatsLog.write(
FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION,
@@ -142,6 +154,32 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION__STATE__UNBLOCKED
);
}
+
+ private void addSeenNotificationKey(String key) {
+ mAllSeenNotificationKeys.add(key);
+ }
+
+ private void addSeenOtpNotificationKey(String key) {
+ mAllSeenNotificationKeys.add(key);
+ mSeenOtpNotificationKeys.add(key);
+ }
+
+ public void addSeenNotifications(
+ @NonNull StatusBarNotification[] notifications,
+ @NonNull RankingMap rankingMap) {
+ for (StatusBarNotification sbn : notifications) {
+ if (sbn == null) {
+ Log.w(TAG, "Unable to parse null notification");
+ continue;
+ }
+
+ if (notificationHasSensitiveContent(sbn, rankingMap)) {
+ addSeenOtpNotificationKey(sbn.getKey());
+ } else {
+ addSeenNotificationKey(sbn.getKey());
+ }
+ }
+ }
}
private final MediaProjectionManager.Callback mProjectionCallback =
@@ -297,6 +335,9 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
mProjectionActive = false;
if (mMediaProjectionSession != null) {
mMediaProjectionSession.logProjectionSessionStop();
+ if (sensitiveContentImprovements()) {
+ mMediaProjectionSession.logAppNotificationsProtected();
+ }
mMediaProjectionSession = null;
}
@@ -334,9 +375,14 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
notifications = new StatusBarNotification[0];
}
+ if (sensitiveContentImprovements() && mMediaProjectionSession != null) {
+ mMediaProjectionSession.addSeenNotifications(notifications, rankingMap);
+ }
+
// notify windowmanager of any currently posted sensitive content notifications
ArraySet<PackageInfo> packageInfos =
getSensitivePackagesFromNotifications(notifications, rankingMap);
+
if (packageInfos.size() > 0) {
mWindowManager.addBlockScreenCaptureForApps(packageInfos);
}
@@ -420,6 +466,14 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
mWindowManager.addBlockScreenCaptureForApps(
new ArraySet(Set.of(packageInfo)));
}
+
+ if (sensitiveContentImprovements() && mMediaProjectionSession != null) {
+ if (packageInfo != null) {
+ mMediaProjectionSession.addSeenOtpNotificationKey(sbn.getKey());
+ } else {
+ mMediaProjectionSession.addSeenNotificationKey(sbn.getKey());
+ }
+ }
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 67e18ca32e21..4dd3a8f67b0d 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -149,7 +149,6 @@ import com.android.internal.os.FuseUnavailableMountException;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
-import com.android.internal.util.HexDump;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.modules.utils.TypedXmlPullParser;
@@ -3278,7 +3277,7 @@ class StorageManagerService extends IStorageManager.Stub
throws RemoteException {
super.setCeStorageProtection_enforcePermission();
- mVold.setCeStorageProtection(userId, HexDump.toHexString(secret));
+ mVold.setCeStorageProtection(userId, secret);
}
/* Only for use by LockSettingsService */
@@ -3288,7 +3287,7 @@ class StorageManagerService extends IStorageManager.Stub
super.unlockCeStorage_enforcePermission();
if (StorageManager.isFileEncrypted()) {
- mVold.unlockCeStorage(userId, HexDump.toHexString(secret));
+ mVold.unlockCeStorage(userId, secret);
}
synchronized (mLock) {
mCeUnlockedUsers.append(userId);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 7ea82b095533..e47d416f9318 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -5725,14 +5725,11 @@ public final class ActiveServices {
bringDownServiceLocked(r, enqueueOomAdj);
return msg;
}
- mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app, r,
- true);
+ mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app,
+ r);
if (isolated) {
r.isolationHostProc = app;
}
- } else {
- mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app, r,
- false);
}
if (r.fgRequired) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 77398476ddaa..4568624ec200 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -37,6 +37,9 @@ import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.ActivityManager.PROCESS_STATE_TOP;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED;
+import static android.app.ActivityManager.RESTRICTION_REASON_DEFAULT;
+import static android.app.ActivityManager.RESTRICTION_REASON_USAGE;
import static android.app.ActivityManager.StopUserOnSwitch;
import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN;
import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN;
@@ -497,6 +500,8 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
+import java.time.Instant;
+import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
@@ -683,8 +688,6 @@ public class ActivityManagerService extends IActivityManager.Stub
public final IntentFirewall mIntentFirewall;
- public OomAdjProfiler mOomAdjProfiler = new OomAdjProfiler();
-
/**
* The global lock for AMS, it's de-facto the ActivityManagerService object as of now.
*/
@@ -2596,7 +2599,6 @@ public class ActivityManagerService extends IActivityManager.Stub
BackgroundThread.getHandler(), this);
mOnBattery = DEBUG_POWER ? true
: mBatteryStatsService.getActiveStatistics().getIsOnBattery();
- mOomAdjProfiler.batteryPowerChanged(mOnBattery);
mProcessStats = new ProcessStatsService(this, new File(systemDir, "procstats"));
@@ -2845,13 +2847,12 @@ public class ActivityManagerService extends IActivityManager.Stub
updateCpuStatsNow();
synchronized (mProcLock) {
mOnBattery = DEBUG_POWER ? true : onBattery;
- mOomAdjProfiler.batteryPowerChanged(onBattery);
}
}
@Override
public void batteryStatsReset() {
- mOomAdjProfiler.reset();
+ // Empty for now.
}
@Override
@@ -5107,10 +5108,19 @@ public class ActivityManagerService extends IActivityManager.Stub
} // else, stopped packages in private space may still hit the logic below
}
}
+
+ final boolean wasForceStopped = app.wasForceStopped()
+ || app.getWindowProcessController().wasForceStopped();
+ if (android.app.Flags.appRestrictionsApi() && wasForceStopped) {
+ noteAppRestrictionEnabled(app.info.packageName, app.uid,
+ RESTRICTION_LEVEL_FORCE_STOPPED, false,
+ RESTRICTION_REASON_USAGE, "unknown", 0L);
+ }
+
if (!sendBroadcast) {
if (!android.content.pm.Flags.stayStopped()) return;
// Nothing to do if it wasn't previously stopped
- if (!app.wasForceStopped() && !app.getWindowProcessController().wasForceStopped()) {
+ if (!wasForceStopped) {
return;
}
}
@@ -7417,7 +7427,6 @@ public class ActivityManagerService extends IActivityManager.Stub
mServices.updateScreenStateLocked(isAwake);
reportCurWakefulnessUsageEvent();
mActivityTaskManager.onScreenAwakeChanged(isAwake);
- mOomAdjProfiler.onWakefulnessChanged(wakefulness);
mOomAdjuster.onWakefulnessChanged(wakefulness);
updateOomAdjLocked(OOM_ADJ_REASON_UI_VISIBILITY);
@@ -8949,8 +8958,10 @@ public class ActivityManagerService extends IActivityManager.Stub
com.android.internal.R.integer.config_multiuserMaxRunningUsers);
final boolean delayUserDataLocking = res.getBoolean(
com.android.internal.R.bool.config_multiuserDelayUserDataLocking);
+ final int backgroundUserScheduledStopTimeSecs = res.getInteger(
+ com.android.internal.R.integer.config_backgroundUserScheduledStopTimeSecs);
mUserController.setInitialConfig(userSwitchUiEnabled, maxRunningUsers,
- delayUserDataLocking);
+ delayUserDataLocking, backgroundUserScheduledStopTimeSecs);
}
mAppErrors.loadAppsNotReportingCrashesFromConfig(res.getString(
com.android.internal.R.string.config_appsNotReportingCrashes));
@@ -9845,6 +9856,11 @@ public class ActivityManagerService extends IActivityManager.Stub
sb.append("Process-Runtime: ").append(runtimeMillis).append("\n");
}
}
+ if (eventType.equals("crash")) {
+ String formattedTime = DROPBOX_TIME_FORMATTER.format(
+ Instant.now().atZone(ZoneId.systemDefault()));
+ sb.append("Timestamp: ").append(formattedTime).append("\n");
+ }
if (activityShortComponentName != null) {
sb.append("Activity: ").append(activityShortComponentName).append("\n");
}
@@ -10542,12 +10558,6 @@ public class ActivityManagerService extends IActivityManager.Stub
pw.println(
"-------------------------------------------------------------------------------");
}
- mOomAdjProfiler.dump(pw);
- pw.println();
- if (dumpAll) {
- pw.println(
- "-------------------------------------------------------------------------------");
- }
dumpLmkLocked(pw);
}
pw.println();
@@ -14331,6 +14341,20 @@ public class ActivityManagerService extends IActivityManager.Stub
int newBackupUid;
synchronized(this) {
+ if (android.app.Flags.appRestrictionsApi()) {
+ try {
+ final boolean wasStopped = mPackageManagerInt.isPackageStopped(app.packageName,
+ UserHandle.getUserId(app.uid));
+ if (wasStopped) {
+ noteAppRestrictionEnabled(app.packageName, app.uid,
+ RESTRICTION_LEVEL_FORCE_STOPPED, false,
+ RESTRICTION_REASON_DEFAULT, "restore", 0L);
+ }
+ } catch (NameNotFoundException e) {
+ Slog.w(TAG, "No such package", e);
+ }
+ }
+
// !!! TODO: currently no check here that we're already bound
// Backup agent is now in use, its package can't be stopped.
try {
@@ -20116,6 +20140,34 @@ public class ActivityManagerService extends IActivityManager.Stub
}
/**
+ * Log the reason for changing an app restriction. Purely used for logging purposes and does not
+ * cause any change to app state.
+ *
+ * @see ActivityManager#noteAppRestrictionEnabled(String, int, int, boolean, int, String, long)
+ */
+ @Override
+ public void noteAppRestrictionEnabled(String packageName, int uid,
+ @RestrictionLevel int restrictionType, boolean enabled,
+ @ActivityManager.RestrictionReason int reason, String subReason, long threshold) {
+ if (!android.app.Flags.appRestrictionsApi()) return;
+
+ enforceCallingPermission(android.Manifest.permission.DEVICE_POWER,
+ "noteAppRestrictionEnabled()");
+
+ final int userId = UserHandle.getCallingUserId();
+ final long callingId = Binder.clearCallingIdentity();
+ try {
+ if (uid == -1) {
+ uid = mPackageManagerInt.getPackageUid(packageName, 0, userId);
+ }
+ mAppRestrictionController.noteAppRestrictionEnabled(packageName, uid, restrictionType,
+ enabled, reason, subReason, threshold);
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ }
+
+ /**
* Get an app's background restriction level.
* This interface is intended for the shell command to use.
*/
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 372ec45763bf..5b15c37b2528 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -4006,8 +4006,12 @@ final class ActivityManagerShellCommand extends ShellCommand {
return ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
case "background_restricted":
return ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
- case "hibernation":
- return ActivityManager.RESTRICTION_LEVEL_HIBERNATION;
+ case "force_stopped":
+ return ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED;
+ case "user_launch_only":
+ return ActivityManager.RESTRICTION_LEVEL_USER_LAUNCH_ONLY;
+ case "custom":
+ return ActivityManager.RESTRICTION_LEVEL_CUSTOM;
default:
return ActivityManager.RESTRICTION_LEVEL_UNKNOWN;
}
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index ef015ee9d743..c5cad14ee3d7 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -23,11 +23,20 @@ import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI;
import static android.app.ActivityManager.RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
import static android.app.ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
import static android.app.ActivityManager.RESTRICTION_LEVEL_EXEMPTED;
-import static android.app.ActivityManager.RESTRICTION_LEVEL_HIBERNATION;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED;
import static android.app.ActivityManager.RESTRICTION_LEVEL_MAX;
import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
import static android.app.ActivityManager.RESTRICTION_LEVEL_UNKNOWN;
import static android.app.ActivityManager.RESTRICTION_LEVEL_UNRESTRICTED;
+import static android.app.ActivityManager.RESTRICTION_LEVEL_USER_LAUNCH_ONLY;
+import static android.app.ActivityManager.RESTRICTION_REASON_DEFAULT;
+import static android.app.ActivityManager.RESTRICTION_REASON_DORMANT;
+import static android.app.ActivityManager.RESTRICTION_REASON_REMOTE_TRIGGER;
+import static android.app.ActivityManager.RESTRICTION_REASON_SYSTEM_HEALTH;
+import static android.app.ActivityManager.RESTRICTION_REASON_USAGE;
+import static android.app.ActivityManager.RESTRICTION_REASON_USER;
+import static android.app.ActivityManager.RESTRICTION_REASON_USER_NUDGED;
+import static android.app.ActivityManager.RESTRICTION_SUBREASON_MAX_LENGTH;
import static android.app.ActivityManager.UID_OBSERVER_ACTIVE;
import static android.app.ActivityManager.UID_OBSERVER_GONE;
import static android.app.ActivityManager.UID_OBSERVER_IDLE;
@@ -93,6 +102,7 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManager.RestrictionLevel;
+import android.app.ActivityManager.RestrictionReason;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerInternal.AppBackgroundRestrictionListener;
import android.app.AppOpsManager;
@@ -334,6 +344,8 @@ public final class AppRestrictionController {
final ActivityManagerService mActivityManagerService;
+ private volatile boolean mLockedBootCompleted = false;
+
static final int TRACKER_TYPE_UNKNOWN = 0;
static final int TRACKER_TYPE_BATTERY = 1;
static final int TRACKER_TYPE_BATTERY_EXEMPTION = 2;
@@ -342,6 +354,7 @@ public final class AppRestrictionController {
static final int TRACKER_TYPE_PERMISSION = 5;
static final int TRACKER_TYPE_BROADCAST_EVENTS = 6;
static final int TRACKER_TYPE_BIND_SERVICE_EVENTS = 7;
+
@IntDef(prefix = { "TRACKER_TYPE_" }, value = {
TRACKER_TYPE_UNKNOWN,
TRACKER_TYPE_BATTERY,
@@ -1712,7 +1725,7 @@ public final class AppRestrictionController {
String packageName, @UsageStatsManager.StandbyBuckets int standbyBucket,
boolean allowRequestBgRestricted, boolean calcTrackers) {
if (mInjector.getAppHibernationInternal().isHibernatingForUser(packageName, userId)) {
- return new Pair<>(RESTRICTION_LEVEL_HIBERNATION, mEmptyTrackerInfo);
+ return new Pair<>(RESTRICTION_LEVEL_FORCE_STOPPED, mEmptyTrackerInfo);
}
@RestrictionLevel int level;
TrackerInfo trackerInfo = null;
@@ -1721,8 +1734,10 @@ public final class AppRestrictionController {
level = RESTRICTION_LEVEL_EXEMPTED;
break;
case STANDBY_BUCKET_NEVER:
- level = RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
- break;
+ if (!android.app.Flags.appRestrictionsApi()) {
+ level = RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
+ break;
+ }
case STANDBY_BUCKET_ACTIVE:
case STANDBY_BUCKET_WORKING_SET:
case STANDBY_BUCKET_FREQUENT:
@@ -1802,7 +1817,9 @@ public final class AppRestrictionController {
case STANDBY_BUCKET_EXEMPTED:
return RESTRICTION_LEVEL_EXEMPTED;
case STANDBY_BUCKET_NEVER:
- return RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
+ if (!android.app.Flags.appRestrictionsApi()) {
+ return RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
+ }
case STANDBY_BUCKET_ACTIVE:
case STANDBY_BUCKET_WORKING_SET:
case STANDBY_BUCKET_FREQUENT:
@@ -2028,7 +2045,7 @@ public final class AppRestrictionController {
return AppBackgroundRestrictionsInfo.LEVEL_RESTRICTED_BUCKET;
case RESTRICTION_LEVEL_BACKGROUND_RESTRICTED:
return AppBackgroundRestrictionsInfo.LEVEL_BACKGROUND_RESTRICTED;
- case RESTRICTION_LEVEL_HIBERNATION:
+ case RESTRICTION_LEVEL_FORCE_STOPPED:
return AppBackgroundRestrictionsInfo.LEVEL_HIBERNATION;
default:
return AppBackgroundRestrictionsInfo.LEVEL_UNKNOWN;
@@ -2214,7 +2231,8 @@ public final class AppRestrictionController {
}
}
- if (doItNow && android.app.Flags.appRestrictionsApi()) {
+ if (doItNow && android.app.Flags.appRestrictionsApi()
+ && curLevel != RESTRICTION_LEVEL_UNKNOWN) {
logAppBackgroundRestrictionInfo(pkgName, uid, curLevel, level, trackerInfo,
reason);
}
@@ -2308,6 +2326,9 @@ public final class AppRestrictionController {
private void handleAppStandbyBucketChanged(int bucket, String packageName,
@UserIdInt int userId) {
+ // Ignore spurious changes to standby bucket during early boot
+ if (android.app.Flags.appRestrictionsApi() && !mLockedBootCompleted) return;
+
final int uid = mInjector.getPackageManagerInternal().getPackageUid(
packageName, STOCK_PM_FLAGS, userId);
final Pair<Integer, TrackerInfo> levelTypePair = calcAppRestrictionLevel(
@@ -2344,6 +2365,85 @@ public final class AppRestrictionController {
}
}
+ /**
+ * Log a change in restriction state with a reason and threshold.
+ * @param packageName
+ * @param uid
+ * @param restrictionType
+ * @param enabled
+ * @param reason
+ * @param subReason Eg: settings, cli, long_wakelock, crash, binder_spam, cpu, threads
+ * Length should not exceed RESTRICTON_SUBREASON_MAX_LENGTH
+ * @param threshold
+ */
+ public void noteAppRestrictionEnabled(String packageName, int uid,
+ @RestrictionLevel int restrictionType, boolean enabled,
+ @RestrictionReason int reason, String subReason, long threshold) {
+ if (DEBUG_BG_RESTRICTION_CONTROLLER) {
+ Slog.i(TAG, (enabled ? "restricted " : "unrestricted ") + packageName + " to "
+ + restrictionType + " reason=" + reason + ", subReason=" + subReason
+ + ", threshold=" + threshold);
+ }
+
+ // Limit the length of the free-form subReason string
+ if (subReason != null && subReason.length() > RESTRICTION_SUBREASON_MAX_LENGTH) {
+ subReason = subReason.substring(0, RESTRICTION_SUBREASON_MAX_LENGTH);
+ Slog.e(TAG, "Subreason is too long, truncating: " + subReason);
+ }
+
+ // Log the restriction reason
+ FrameworkStatsLog.write(FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED, uid,
+ getRestrictionTypeStatsd(restrictionType),
+ enabled,
+ getRestrictionChangeReasonStatsd(reason, subReason),
+ subReason,
+ threshold);
+ }
+
+ private int getRestrictionTypeStatsd(@RestrictionLevel int level) {
+ return switch (level) {
+ case RESTRICTION_LEVEL_UNKNOWN ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_UNKNOWN;
+ case RESTRICTION_LEVEL_UNRESTRICTED ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_UNRESTRICTED;
+ case RESTRICTION_LEVEL_EXEMPTED ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_EXEMPTED;
+ case RESTRICTION_LEVEL_ADAPTIVE_BUCKET ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_ADAPTIVE;
+ case RESTRICTION_LEVEL_RESTRICTED_BUCKET ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_RESTRICTED_BUCKET;
+ case RESTRICTION_LEVEL_BACKGROUND_RESTRICTED ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_BACKGROUND_RESTRICTED;
+ case RESTRICTION_LEVEL_FORCE_STOPPED ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_FORCE_STOPPED;
+ case RESTRICTION_LEVEL_USER_LAUNCH_ONLY ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_USER_LAUNCH_ONLY;
+ default ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_CUSTOM;
+ };
+ }
+
+ private int getRestrictionChangeReasonStatsd(int reason, String subReason) {
+ return switch (reason) {
+ case RESTRICTION_REASON_DEFAULT ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_DEFAULT;
+ case RESTRICTION_REASON_DORMANT ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_DORMANT;
+ case RESTRICTION_REASON_USAGE ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_USAGE;
+ case RESTRICTION_REASON_USER ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_USER;
+ case RESTRICTION_REASON_USER_NUDGED ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_USER_NUDGED;
+ case RESTRICTION_REASON_SYSTEM_HEALTH ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_SYSTEM_HEALTH;
+ case RESTRICTION_REASON_REMOTE_TRIGGER ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_REMOTE_TRIGGER;
+ default ->
+ FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_OTHER;
+ };
+ }
+
static class NotificationHelper {
static final String PACKAGE_SCHEME = "package";
static final String GROUP_KEY = "com.android.app.abusive_bg_apps";
@@ -3391,6 +3491,7 @@ public final class AppRestrictionController {
for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
mAppStateTrackers.get(i).onLockedBootCompleted();
}
+ mLockedBootCompleted = true;
}
boolean isBgAutoRestrictedBucketFeatureFlagEnabled() {
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index 0728ea8e5604..2be1fe26628e 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -196,6 +196,8 @@ public final class AppStartInfoTracker {
start.setIntent(intent);
start.setStartType(ApplicationStartInfo.START_TYPE_UNSET);
start.addStartupTimestamp(ApplicationStartInfo.START_TIMESTAMP_LAUNCH, timestampNanos);
+
+ // TODO: handle possible alarm activity start.
if (intent != null && intent.getCategories() != null
&& intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER);
@@ -313,7 +315,7 @@ public final class AppStartInfoTracker {
}
public void handleProcessServiceStart(long startTimeNs, ProcessRecord app,
- ServiceRecord serviceRecord, boolean cold) {
+ ServiceRecord serviceRecord) {
synchronized (mLock) {
if (!mEnabled) {
return;
@@ -323,8 +325,9 @@ public final class AppStartInfoTracker {
start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
start.addStartupTimestamp(
ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
- start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD
- : ApplicationStartInfo.START_TYPE_WARM);
+ start.setStartType(ApplicationStartInfo.START_TYPE_COLD);
+
+ // TODO: handle possible alarm service start.
start.setReason(serviceRecord.permission != null
&& serviceRecord.permission.contains("android.permission.BIND_JOB_SERVICE")
? ApplicationStartInfo.START_REASON_JOB
@@ -336,8 +339,9 @@ public final class AppStartInfoTracker {
}
}
- public void handleProcessBroadcastStart(long startTimeNs, ProcessRecord app,
- BroadcastRecord broadcast, boolean cold) {
+ /** Process a broadcast triggered app start. */
+ public void handleProcessBroadcastStart(long startTimeNs, ProcessRecord app, Intent intent,
+ boolean isAlarm) {
synchronized (mLock) {
if (!mEnabled) {
return;
@@ -347,26 +351,19 @@ public final class AppStartInfoTracker {
start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
start.addStartupTimestamp(
ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
- start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD
- : ApplicationStartInfo.START_TYPE_WARM);
- if (broadcast == null) {
- start.setReason(ApplicationStartInfo.START_REASON_BROADCAST);
- } else if (broadcast.alarm) {
+ start.setStartType(ApplicationStartInfo.START_TYPE_COLD);
+ if (isAlarm) {
start.setReason(ApplicationStartInfo.START_REASON_ALARM);
- } else if (broadcast.pushMessage || broadcast.pushMessageOverQuota) {
- start.setReason(ApplicationStartInfo.START_REASON_PUSH);
- } else if (Intent.ACTION_BOOT_COMPLETED.equals(broadcast.intent.getAction())) {
- start.setReason(ApplicationStartInfo.START_REASON_BOOT_COMPLETE);
} else {
start.setReason(ApplicationStartInfo.START_REASON_BROADCAST);
}
- start.setIntent(broadcast != null ? broadcast.intent : null);
+ start.setIntent(intent);
addStartInfoLocked(start);
}
}
- public void handleProcessContentProviderStart(long startTimeNs, ProcessRecord app,
- boolean cold) {
+ /** Process a content provider triggered app start. */
+ public void handleProcessContentProviderStart(long startTimeNs, ProcessRecord app) {
synchronized (mLock) {
if (!mEnabled) {
return;
@@ -376,8 +373,7 @@ public final class AppStartInfoTracker {
start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
start.addStartupTimestamp(
ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
- start.setStartType(cold ? ApplicationStartInfo.START_TYPE_COLD
- : ApplicationStartInfo.START_TYPE_WARM);
+ start.setStartType(ApplicationStartInfo.START_TYPE_COLD);
start.setReason(ApplicationStartInfo.START_REASON_CONTENT_PROVIDER);
addStartInfoLocked(start);
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index c08288901157..48dd03922479 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -1030,6 +1030,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
"startProcessLocked failed");
return true;
}
+ // TODO: b/335420031 - cache receiver intent to avoid multiple calls to getReceiverIntent.
+ mService.mProcessList.getAppStartInfoTracker().handleProcessBroadcastStart(
+ SystemClock.elapsedRealtimeNanos(), queue.app, r.getReceiverIntent(receiver),
+ r.alarm /* isAlarm */);
return false;
}
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index f76bf37e4d58..28fd1977539c 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -179,6 +179,7 @@ public class ContentProviderHelper {
final int expectedUserId = userId;
synchronized (mService) {
long startTime = SystemClock.uptimeMillis();
+ long startElapsedTimeNs = SystemClock.elapsedRealtimeNanos();
ProcessRecord r = null;
if (caller != null) {
@@ -585,6 +586,8 @@ public class ContentProviderHelper {
callingProcessState, ActivityManager.PROCESS_STATE_NONEXISTENT,
firstLaunch,
0L /* TODO: stoppedDuration */);
+ mService.mProcessList.getAppStartInfoTracker()
+ .handleProcessContentProviderStart(startElapsedTimeNs, proc);
}
cpr.launchingApp = proc;
mLaunchingProviders.add(cpr);
diff --git a/services/core/java/com/android/server/am/OomAdjProfiler.java b/services/core/java/com/android/server/am/OomAdjProfiler.java
deleted file mode 100644
index 08691145bf6e..000000000000
--- a/services/core/java/com/android/server/am/OomAdjProfiler.java
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * Copyright (C) 2018 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.am;
-
-import android.os.Message;
-import android.os.PowerManagerInternal;
-import android.os.Process;
-import android.os.SystemClock;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.os.BackgroundThread;
-import com.android.internal.os.ProcessCpuTracker;
-import com.android.internal.util.RingBuffer;
-import com.android.internal.util.function.pooled.PooledLambda;
-
-import java.io.PrintWriter;
-
-public class OomAdjProfiler {
- private static final int MSG_UPDATE_CPU_TIME = 42;
-
- @GuardedBy("this")
- private boolean mOnBattery;
- @GuardedBy("this")
- private boolean mScreenOff;
-
- /** The value of {@link #mOnBattery} when the CPU time update was last scheduled. */
- @GuardedBy("this")
- private boolean mLastScheduledOnBattery;
- /** The value of {@link #mScreenOff} when the CPU time update was last scheduled. */
- @GuardedBy("this")
- private boolean mLastScheduledScreenOff;
-
- @GuardedBy("this")
- private long mOomAdjStartTimeUs;
- @GuardedBy("this")
- private boolean mOomAdjStarted;
-
- @GuardedBy("this")
- private CpuTimes mOomAdjRunTime = new CpuTimes();
- @GuardedBy("this")
- private CpuTimes mSystemServerCpuTime = new CpuTimes();
-
- @GuardedBy("this")
- private long mLastSystemServerCpuTimeMs;
- @GuardedBy("this")
- private boolean mSystemServerCpuTimeUpdateScheduled;
- private final ProcessCpuTracker mProcessCpuTracker = new ProcessCpuTracker(false);
-
- @GuardedBy("this")
- final RingBuffer<CpuTimes> mOomAdjRunTimesHist = new RingBuffer<>(CpuTimes.class, 10);
- @GuardedBy("this")
- final RingBuffer<CpuTimes> mSystemServerCpuTimesHist = new RingBuffer<>(CpuTimes.class, 10);
-
- @GuardedBy("this")
- private long mTotalOomAdjRunTimeUs;
- @GuardedBy("this")
- private int mTotalOomAdjCalls;
-
- void batteryPowerChanged(boolean onBattery) {
- synchronized (this) {
- scheduleSystemServerCpuTimeUpdate();
- mOnBattery = onBattery;
- }
- }
-
- void onWakefulnessChanged(int wakefulness) {
- synchronized (this) {
- scheduleSystemServerCpuTimeUpdate();
- mScreenOff = wakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE;
- }
- }
-
- void oomAdjStarted() {
- synchronized (this) {
- mOomAdjStartTimeUs = SystemClock.currentThreadTimeMicro();
- mOomAdjStarted = true;
- }
- }
-
- void oomAdjEnded() {
- synchronized (this) {
- if (!mOomAdjStarted) {
- return;
- }
- long elapsedUs = SystemClock.currentThreadTimeMicro() - mOomAdjStartTimeUs;
- mOomAdjRunTime.addCpuTimeUs(elapsedUs);
- mTotalOomAdjRunTimeUs += elapsedUs;
- mTotalOomAdjCalls++;
- }
- }
-
- private void scheduleSystemServerCpuTimeUpdate() {
- synchronized (this) {
- if (mSystemServerCpuTimeUpdateScheduled) {
- return;
- }
- mLastScheduledOnBattery = mOnBattery;
- mLastScheduledScreenOff = mScreenOff;
- mSystemServerCpuTimeUpdateScheduled = true;
- Message scheduledMessage = PooledLambda.obtainMessage(
- OomAdjProfiler::updateSystemServerCpuTime,
- this, mLastScheduledOnBattery, mLastScheduledScreenOff, true);
- scheduledMessage.setWhat(MSG_UPDATE_CPU_TIME);
-
- BackgroundThread.getHandler().sendMessage(scheduledMessage);
- }
- }
-
- private void updateSystemServerCpuTime(boolean onBattery, boolean screenOff,
- boolean onlyIfScheduled) {
- final long cpuTimeMs = mProcessCpuTracker.getCpuTimeForPid(Process.myPid());
- synchronized (this) {
- if (onlyIfScheduled && !mSystemServerCpuTimeUpdateScheduled) {
- return;
- }
- mSystemServerCpuTime.addCpuTimeMs(
- cpuTimeMs - mLastSystemServerCpuTimeMs, onBattery, screenOff);
- mLastSystemServerCpuTimeMs = cpuTimeMs;
- mSystemServerCpuTimeUpdateScheduled = false;
- }
- }
-
- void reset() {
- synchronized (this) {
- if (mSystemServerCpuTime.isEmpty()) {
- return;
- }
- mOomAdjRunTimesHist.append(mOomAdjRunTime);
- mSystemServerCpuTimesHist.append(mSystemServerCpuTime);
- mOomAdjRunTime = new CpuTimes();
- mSystemServerCpuTime = new CpuTimes();
- }
- }
-
- void dump(PrintWriter pw) {
- synchronized (this) {
- if (mSystemServerCpuTimeUpdateScheduled) {
- // Cancel the scheduled update since we're going to update it here instead.
- BackgroundThread.getHandler().removeMessages(MSG_UPDATE_CPU_TIME);
- // Make sure the values are attributed to the right states.
- updateSystemServerCpuTime(mLastScheduledOnBattery, mLastScheduledScreenOff, false);
- } else {
- updateSystemServerCpuTime(mOnBattery, mScreenOff, false);
- }
-
- pw.println("System server and oomAdj runtimes (ms) in recent battery sessions "
- + "(most recent first):");
- if (!mSystemServerCpuTime.isEmpty()) {
- pw.print(" ");
- pw.print("system_server=");
- pw.print(mSystemServerCpuTime);
- pw.print(" ");
- pw.print("oom_adj=");
- pw.println(mOomAdjRunTime);
- }
- final CpuTimes[] systemServerCpuTimes = mSystemServerCpuTimesHist.toArray();
- final CpuTimes[] oomAdjRunTimes = mOomAdjRunTimesHist.toArray();
- for (int i = oomAdjRunTimes.length - 1; i >= 0; --i) {
- pw.print(" ");
- pw.print("system_server=");
- pw.print(systemServerCpuTimes[i]);
- pw.print(" ");
- pw.print("oom_adj=");
- pw.println(oomAdjRunTimes[i]);
- }
- if (mTotalOomAdjCalls != 0) {
- pw.println("System server total oomAdj runtimes (us) since boot:");
- pw.print(" cpu time spent=");
- pw.print(mTotalOomAdjRunTimeUs);
- pw.print(" number of calls=");
- pw.print(mTotalOomAdjCalls);
- pw.print(" average=");
- pw.println(mTotalOomAdjRunTimeUs / mTotalOomAdjCalls);
- }
- }
- }
-
- private class CpuTimes {
- private long mOnBatteryTimeUs;
- private long mOnBatteryScreenOffTimeUs;
-
- public void addCpuTimeMs(long cpuTimeMs) {
- addCpuTimeUs(cpuTimeMs * 1000, mOnBattery, mScreenOff);
- }
-
- public void addCpuTimeMs(long cpuTimeMs, boolean onBattery, boolean screenOff) {
- addCpuTimeUs(cpuTimeMs * 1000, onBattery, screenOff);
- }
-
- public void addCpuTimeUs(long cpuTimeUs) {
- addCpuTimeUs(cpuTimeUs, mOnBattery, mScreenOff);
- }
-
- public void addCpuTimeUs(long cpuTimeUs, boolean onBattery, boolean screenOff) {
- if (onBattery) {
- mOnBatteryTimeUs += cpuTimeUs;
- if (screenOff) {
- mOnBatteryScreenOffTimeUs += cpuTimeUs;
- }
- }
- }
-
- public boolean isEmpty() {
- return mOnBatteryTimeUs == 0 && mOnBatteryScreenOffTimeUs == 0;
- }
-
- public String toString() {
- return "[" + (mOnBatteryTimeUs / 1000) + ","
- + (mOnBatteryScreenOffTimeUs / 1000) + "]";
- }
- }
-}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index ea7a21dd19cd..9b72db8f5d6f 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -600,7 +600,6 @@ public class OomAdjuster {
mLastReason = oomAdjReason;
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
- mService.mOomAdjProfiler.oomAdjStarted();
final ProcessStateRecord state = app.mState;
@@ -630,7 +629,6 @@ public class OomAdjuster {
}
mTmpProcessList.clear();
mService.clearPendingTopAppLocked();
- mService.mOomAdjProfiler.oomAdjEnded();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
return true;
}
@@ -849,7 +847,6 @@ public class OomAdjuster {
mLastReason = oomAdjReason;
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
- mService.mOomAdjProfiler.oomAdjStarted();
mProcessStateCurTop = enqueuePendingTopAppIfNecessaryLSP();
final ArrayList<ProcessRecord> processes = mTmpProcessList;
@@ -862,7 +859,6 @@ public class OomAdjuster {
processes.clear();
mService.clearPendingTopAppLocked();
- mService.mOomAdjProfiler.oomAdjEnded();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -895,7 +891,6 @@ public class OomAdjuster {
mLastReason = oomAdjReason;
if (startProfiling) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
- mService.mOomAdjProfiler.oomAdjStarted();
}
final long now = SystemClock.uptimeMillis();
final long nowElapsed = SystemClock.elapsedRealtime();
@@ -989,7 +984,6 @@ public class OomAdjuster {
postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime, true);
if (startProfiling) {
- mService.mOomAdjProfiler.oomAdjEnded();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index 00e148287b67..3268b661df65 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -744,11 +744,9 @@ public class OomAdjusterModernImpl extends OomAdjuster {
mLastReason = oomAdjReason;
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
- mService.mOomAdjProfiler.oomAdjStarted();
fullUpdateLSP(oomAdjReason);
- mService.mOomAdjProfiler.oomAdjEnded();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -766,14 +764,12 @@ public class OomAdjusterModernImpl extends OomAdjuster {
mLastReason = oomAdjReason;
mProcessStateCurTop = enqueuePendingTopAppIfNecessaryLSP();
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
- mService.mOomAdjProfiler.oomAdjStarted();
synchronized (mProcLock) {
partialUpdateLSP(oomAdjReason, mPendingProcessSet);
}
mPendingProcessSet.clear();
- mService.mOomAdjProfiler.oomAdjEnded();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index dd4cee47bee9..b7030765d053 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -59,6 +59,7 @@ import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_UNLOC
import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_UNLOCKING_USER;
import static com.android.server.pm.UserJourneyLogger.USER_LIFECYCLE_EVENT_USER_RUNNING_LOCKED;
import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
+import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND;
import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND_VISIBLE;
import static com.android.server.pm.UserManagerInternal.USER_START_MODE_FOREGROUND;
import static com.android.server.pm.UserManagerInternal.userAssignmentResultToString;
@@ -200,6 +201,7 @@ class UserController implements Handler.Callback {
static final int START_USER_SWITCH_FG_MSG = 120;
static final int COMPLETE_USER_SWITCH_MSG = 130;
static final int USER_COMPLETED_EVENT_MSG = 140;
+ static final int SCHEDULED_STOP_BACKGROUND_USER_MSG = 150;
private static final int NO_ARG2 = 0;
@@ -251,6 +253,14 @@ class UserController implements Handler.Callback {
@GuardedBy("mLock")
private int mMaxRunningUsers;
+ /**
+ * Number of seconds of uptime after a full user enters the background before we attempt
+ * to stop it due to inactivity. Set to -1 to disable scheduling stopping background users.
+ *
+ * Typically set by config_backgroundUserScheduledStopTimeSecs.
+ */
+ private int mBackgroundUserScheduledStopTimeSecs = -1;
+
// Lock for internal state.
private final Object mLock = new Object();
@@ -453,11 +463,12 @@ class UserController implements Handler.Callback {
}
void setInitialConfig(boolean userSwitchUiEnabled, int maxRunningUsers,
- boolean delayUserDataLocking) {
+ boolean delayUserDataLocking, int backgroundUserScheduledStopTimeSecs) {
synchronized (mLock) {
mUserSwitchUiEnabled = userSwitchUiEnabled;
mMaxRunningUsers = maxRunningUsers;
mDelayUserDataLocking = delayUserDataLocking;
+ mBackgroundUserScheduledStopTimeSecs = backgroundUserScheduledStopTimeSecs;
mInitialized = true;
}
}
@@ -1091,6 +1102,10 @@ class UserController implements Handler.Callback {
final IStopUserCallback stopUserCallback,
KeyEvictedCallback keyEvictedCallback) {
Slogf.i(TAG, "stopSingleUserLU userId=" + userId);
+ if (android.multiuser.Flags.scheduleStopOfBackgroundUser()) {
+ mHandler.removeEqualMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG,
+ Integer.valueOf(userId));
+ }
final UserState uss = mStartedUsers.get(userId);
if (uss == null) { // User is not started
// If canDelayDataLockingForUser() is true and allowDelayedLocking is false, we need
@@ -1879,6 +1894,10 @@ class UserController implements Handler.Callback {
// No matter what, the fact that we're requested to start the user (even if it is
// already running) puts it towards the end of the mUserLru list.
addUserToUserLru(userId);
+ if (android.multiuser.Flags.scheduleStopOfBackgroundUser()) {
+ mHandler.removeEqualMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG,
+ Integer.valueOf(userId));
+ }
if (unlockListener != null) {
uss.mUnlockProgress.addListener(unlockListener);
@@ -1923,6 +1942,9 @@ class UserController implements Handler.Callback {
// of mUserLru, so we need to ensure that the foreground user isn't displaced.
addUserToUserLru(mCurrentUserId);
}
+ if (userStartMode == USER_START_MODE_BACKGROUND && !userInfo.isProfile()) {
+ scheduleStopOfBackgroundUser(userId);
+ }
t.traceEnd();
// Make sure user is in the started state. If it is currently
@@ -2294,6 +2316,65 @@ class UserController implements Handler.Callback {
}
}
+ /**
+ * Possibly schedules the user to be stopped at a future point. To be used to stop background
+ * users that haven't been actively used in a long time.
+ * This is only intended for full users that are currently in the background.
+ */
+ private void scheduleStopOfBackgroundUser(@UserIdInt int oldUserId) {
+ if (!android.multiuser.Flags.scheduleStopOfBackgroundUser()) {
+ return;
+ }
+ final int delayUptimeSecs = mBackgroundUserScheduledStopTimeSecs;
+ if (delayUptimeSecs <= 0 || UserManager.isVisibleBackgroundUsersEnabled()) {
+ // Feature is not enabled on this device.
+ return;
+ }
+ if (oldUserId == UserHandle.USER_SYSTEM) {
+ // Never stop system user
+ return;
+ }
+ if (oldUserId == mInjector.getUserManagerInternal().getMainUserId()) {
+ // MainUser is currently special for things like Docking, so we'll exempt it for now.
+ Slogf.i(TAG, "Exempting user %d from being stopped due to inactivity by virtue "
+ + "of it being the main user", oldUserId);
+ return;
+ }
+ Slogf.d(TAG, "Scheduling to stop user %d in %d seconds", oldUserId, delayUptimeSecs);
+ final int delayUptimeMs = delayUptimeSecs * 1000;
+ final Object msgObj = oldUserId;
+ mHandler.removeEqualMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, msgObj);
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(SCHEDULED_STOP_BACKGROUND_USER_MSG, msgObj),
+ delayUptimeMs);
+ }
+
+ /**
+ * Possibly stops the given full user due to it having been in the background for a long time.
+ * There is no guarantee of stopping the user; it is done discretionarily.
+ *
+ * This should never be called for background visible users; devices that support this should
+ * not use {@link #scheduleStopOfBackgroundUser(int)}.
+ *
+ * @param userIdInteger a full user to be stopped if it is still in the background
+ */
+ @VisibleForTesting
+ void processScheduledStopOfBackgroundUser(Integer userIdInteger) {
+ final int userId = userIdInteger;
+ Slogf.d(TAG, "Considering stopping background user %d due to inactivity", userId);
+ synchronized (mLock) {
+ if (getCurrentOrTargetUserIdLU() == userId) {
+ return;
+ }
+ if (mPendingTargetUserIds.contains(userIdInteger)) {
+ // We'll soon want to switch to this user, so don't kill it now.
+ return;
+ }
+ Slogf.i(TAG, "Stopping background user %d due to inactivity", userId);
+ stopUsersLU(userId, /* allowDelayedLocking= */ true, null, null);
+ }
+ }
+
private void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) {
TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
t.traceBegin("timeoutUserSwitch-" + oldUserId + "-to-" + newUserId);
@@ -2428,6 +2509,7 @@ class UserController implements Handler.Callback {
uss.switching = false;
stopGuestOrEphemeralUserIfBackground(oldUserId);
stopUserOnSwitchIfEnforced(oldUserId);
+ scheduleStopOfBackgroundUser(oldUserId);
t.traceEnd(); // end continueUserSwitch
}
@@ -3309,6 +3391,8 @@ class UserController implements Handler.Callback {
pw.println(" shouldStopUserOnSwitch():" + shouldStopUserOnSwitch());
pw.println(" mStopUserOnSwitch:" + mStopUserOnSwitch);
pw.println(" mMaxRunningUsers:" + mMaxRunningUsers);
+ pw.println(" mBackgroundUserScheduledStopTimeSecs:"
+ + mBackgroundUserScheduledStopTimeSecs);
pw.println(" mUserSwitchUiEnabled:" + mUserSwitchUiEnabled);
pw.println(" mInitialized:" + mInitialized);
pw.println(" mIsBroadcastSentForSystemUserStarted:"
@@ -3435,6 +3519,9 @@ class UserController implements Handler.Callback {
case COMPLETE_USER_SWITCH_MSG:
completeUserSwitch(msg.arg1, msg.arg2);
break;
+ case SCHEDULED_STOP_BACKGROUND_USER_MSG:
+ processScheduledStopOfBackgroundUser((Integer) msg.obj);
+ break;
}
return false;
}
diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
index a17b3d579890..ce410796137b 100644
--- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java
+++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
@@ -461,6 +461,9 @@ public final class AppHibernationService extends SystemService {
packageName, PACKAGE_MATCH_FLAGS, userId);
StorageStats stats = mStorageStatsManager.queryStatsForPackage(
info.storageUuid, packageName, new UserHandle(userId));
+ if (android.app.Flags.appRestrictionsApi()) {
+ noteHibernationChange(packageName, info.uid, true);
+ }
mIActivityManager.forceStopPackage(packageName, userId);
mIPackageManager.deleteApplicationCacheFilesAsUser(packageName, userId,
null /* observer */);
@@ -490,6 +493,11 @@ public final class AppHibernationService extends SystemService {
// Deliver LOCKED_BOOT_COMPLETE AND BOOT_COMPLETE broadcast so app can re-register
// their alarms/jobs/etc.
try {
+ if (android.app.Flags.appRestrictionsApi()) {
+ ApplicationInfo info = mIPackageManager.getApplicationInfo(
+ packageName, PACKAGE_MATCH_FLAGS, userId);
+ noteHibernationChange(packageName, info.uid, false);
+ }
Intent lockedBcIntent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED)
.setPackage(packageName);
final String[] requiredPermissions = {Manifest.permission.RECEIVE_BOOT_COMPLETED};
@@ -555,6 +563,26 @@ public final class AppHibernationService extends SystemService {
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
+ /** Inform ActivityManager that the app being stopped or unstopped due to hibernation */
+ private void noteHibernationChange(String packageName, int uid, boolean hibernated) {
+ try {
+ if (hibernated) {
+ // TODO: Switch to an ActivityManagerInternal API
+ mIActivityManager.noteAppRestrictionEnabled(
+ packageName, uid, ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED,
+ true, ActivityManager.RESTRICTION_REASON_DORMANT, null,
+ /* TODO: fetch actual timeout - 90 days */ 90 * 24 * 60 * 60_000L);
+ } else {
+ mIActivityManager.noteAppRestrictionEnabled(
+ packageName, uid, ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED,
+ false, ActivityManager.RESTRICTION_REASON_USAGE, null,
+ 0L);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Couldn't set restriction state change");
+ }
+ }
+
/**
* Initializes in-memory store of user-level hibernation states for the given user
*
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index a1f80d05fe1f..1bd93e4dbc36 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -5543,6 +5543,20 @@ public class AppOpsService extends IAppOpsService.Stub {
}
return 0;
}
+ case "note": {
+ int res = shell.parseUserPackageOp(true, err);
+ if (res < 0) {
+ return res;
+ }
+ if (shell.packageName != null) {
+ shell.mInterface.noteOperation(shell.op, shell.packageUid,
+ shell.packageName, shell.attributionTag, true,
+ "appops note shell command", true);
+ } else {
+ return -1;
+ }
+ return 0;
+ }
case "start": {
int res = shell.parseUserPackageOp(true, err);
if (res < 0) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index f38b38154bc3..9bdc51efb76f 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -987,9 +987,9 @@ public class AudioDeviceInventory {
}
if (di.mPeerDeviceAddress.equals("")) {
for (Pair<String, String> addr : addresses) {
- if (!addr.first.equals(di.mDeviceAddress)) {
- di.mPeerDeviceAddress = addr.first;
- di.mPeerIdentityDeviceAddress = addr.second;
+ if (!di.mDeviceAddress.equals(addr.first)) {
+ di.mPeerDeviceAddress = TextUtils.emptyIfNull(addr.first);
+ di.mPeerIdentityDeviceAddress = TextUtils.emptyIfNull(addr.second);
break;
}
}
@@ -1000,8 +1000,8 @@ public class AudioDeviceInventory {
}
if (di.mDeviceIdentityAddress.equals("")) {
for (Pair<String, String> addr : addresses) {
- if (addr.first.equals(di.mDeviceAddress)) {
- di.mDeviceIdentityAddress = addr.second;
+ if (di.mDeviceAddress.equals(addr.first)) {
+ di.mDeviceIdentityAddress = TextUtils.emptyIfNull(addr.second);
break;
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index c11fbe19a5d0..add84910bf48 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -176,6 +176,7 @@ import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.HwBinder;
import android.os.IBinder;
import android.os.Looper;
@@ -686,6 +687,9 @@ public class AudioService extends IAudioService.Stub
private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES =
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH);
+ // Handler for broadcast receiver
+ // TODO(b/335513647) combine handlers
+ private final HandlerThread mBroadcastHandlerThread;
// Broadcast receiver for device connections intent broadcasts
private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver();
@@ -1121,6 +1125,9 @@ public class AudioService extends IAudioService.Stub
mAudioPolicy = audioPolicy;
mPlatformType = AudioSystem.getPlatformType(context);
+ mBroadcastHandlerThread = new HandlerThread("AudioService Broadcast");
+ mBroadcastHandlerThread.start();
+
mDeviceBroker = new AudioDeviceBroker(mContext, this, mAudioSystem);
mIsSingleVolume = AudioSystem.isSingleVolume(context);
@@ -1507,7 +1514,8 @@ public class AudioService extends IAudioService.Stub
intentFilter.addAction(ACTION_CHECK_MUSIC_ACTIVE);
intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
- mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null,
+ mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null,
+ mBroadcastHandlerThread.getThreadHandler(),
Context.RECEIVER_EXPORTED);
SubscriptionManager subscriptionManager = mContext.getSystemService(
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index edeabdc5243c..a649d34884a7 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -1110,6 +1110,12 @@ public class BtHelper {
return mLeAudio.getGroupId(device);
}
+ /**
+ * Returns all addresses and identity addresses for LE Audio devices a group.
+ * @param groupId The ID of the group from which to get addresses.
+ * @return A List of Pair(String main_address, String identity_address). Note that the
+ * addresses returned by BluetoothDevice can be null.
+ */
/*package*/ List<Pair<String, String>> getLeAudioGroupAddresses(int groupId) {
List<Pair<String, String>> addresses = new ArrayList<>();
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig
index 7a9491e44cd7..92fd9cbcf14e 100644
--- a/services/core/java/com/android/server/biometrics/biometrics.aconfig
+++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig
@@ -9,8 +9,8 @@ flag {
}
flag {
- name: "de_hidl"
- namespace: "biometrics_framework"
- description: "feature flag for biometrics de-hidl"
- bug: "287332354"
-} \ No newline at end of file
+ name: "use_vhal_for_testing"
+ namespace: "biometrics_framework"
+ description: "This flag controls whether virtual HAL is used for testing instead of TestHal "
+ bug: "294254230"
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index beb3f2f2bb71..9c8d98d2d3d2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -34,6 +34,7 @@ import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.IVirtualHal;
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.biometrics.fingerprint.SensorProps;
import android.hardware.fingerprint.Fingerprint;
@@ -59,6 +60,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
import com.android.server.biometrics.AuthenticationStatsCollector;
import com.android.server.biometrics.BiometricHandlerProvider;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
@@ -133,6 +135,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
@Nullable private ISidefpsController mSidefpsController;
private final AuthSessionCoordinator mAuthSessionCoordinator;
@Nullable private AuthenticationStatsCollector mAuthenticationStatsCollector;
+ @Nullable private IVirtualHal mVhal;
+ @Nullable private String mHalInstanceNameCurrent;
private final class BiometricTaskStackListener extends TaskStackListener {
@Override
@@ -293,10 +297,29 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
@VisibleForTesting
synchronized IFingerprint getHalInstance() {
if (mTestHalEnabled) {
- // Enabling the test HAL for a single sensor in a multi-sensor HAL currently enables
- // the test HAL for all sensors under that HAL. This can be updated in the future if
- // necessary.
- return new TestHal();
+ if (Flags.useVhalForTesting()) {
+ if (!mHalInstanceNameCurrent.contains("virtual")) {
+ Slog.i(getTag(), "Switching fingerprint hal from " + mHalInstanceName
+ + " to virtual hal");
+ mHalInstanceNameCurrent = "virtual";
+ mDaemon = null;
+ }
+ } else {
+ // Enabling the test HAL for a single sensor in a multi-sensor HAL currently enables
+ // the test HAL for all sensors under that HAL. This can be updated in the future if
+ // necessary.
+ return new TestHal();
+ }
+ } else {
+ if (mHalInstanceNameCurrent == null) {
+ mHalInstanceNameCurrent = mHalInstanceName;
+ } else if (mHalInstanceNameCurrent.contains("virtual")
+ && mHalInstanceNameCurrent != mHalInstanceName) {
+ Slog.i(getTag(), "Switching fingerprint from virtual hal " + "to "
+ + mHalInstanceName);
+ mHalInstanceNameCurrent = mHalInstanceName;
+ mDaemon = null;
+ }
}
if (mDaemon != null) {
@@ -308,7 +331,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
mDaemon = IFingerprint.Stub.asInterface(
Binder.allowBlocking(
ServiceManager.waitForDeclaredService(
- IFingerprint.DESCRIPTOR + "/" + mHalInstanceName)));
+ IFingerprint.DESCRIPTOR + "/" + mHalInstanceNameCurrent)));
if (mDaemon == null) {
Slog.e(getTag(), "Unable to get daemon");
return null;
@@ -952,4 +975,26 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
public void sendFingerprintReEnrollNotification() {
mAuthenticationStatsCollector.sendFingerprintReEnrollNotification();
}
+
+ /**
+ * Return virtual hal AIDL interface if it is used for testing
+ *
+ */
+ public IVirtualHal getVhal() throws RemoteException {
+ if (mVhal == null && useVhalForTesting()) {
+ mVhal = IVirtualHal.Stub.asInterface(mDaemon.asBinder().getExtension());
+ if (mVhal == null) {
+ Slog.e(getTag(), "Unable to get virtual hal interface");
+ }
+ }
+
+ return mVhal;
+ }
+
+ /**
+ * Return true if vhal_for_testing feature is enabled and test is active
+ */
+ public boolean useVhalForTesting() {
+ return (Flags.useVhalForTesting() && mTestHalEnabled);
+ }
}
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 9f4b3d256b1e..c393e921d957 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -1651,7 +1651,7 @@ public final class ContentService extends IContentService.Stub {
return AppBackgroundRestrictionsInfo.LEVEL_RESTRICTED_BUCKET;
case ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED:
return AppBackgroundRestrictionsInfo.LEVEL_BACKGROUND_RESTRICTED;
- case ActivityManager.RESTRICTION_LEVEL_HIBERNATION:
+ case ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED:
return AppBackgroundRestrictionsInfo.LEVEL_HIBERNATION;
default:
return AppBackgroundRestrictionsInfo.LEVEL_UNKNOWN;
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index df5f0079765c..68e2bd685fac 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -5271,13 +5271,5 @@ public final class DisplayManagerService extends SystemService {
public ExternalDisplayStatsService getExternalDisplayStatsService() {
return mExternalDisplayStatsService;
}
-
- /**
- * Called on external display is ready to be enabled.
- */
- @Override
- public void onExternalDisplayReadyToBeEnabled(int displayId) {
- mDisplayModeDirector.onExternalDisplayReadyToBeEnabled(displayId);
- }
}
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index cfdb75f418de..896670e449f1 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1406,42 +1406,47 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
setBrightnessFromOffload(PowerManager.BRIGHTNESS_INVALID_FLOAT);
}
- // AutomaticBrightnessStrategy has higher priority than OffloadBrightnessStrategy
- if (!mFlags.isRefactorDisplayPowerControllerEnabled() && (Float.isNaN(brightnessState)
- || mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_OFFLOAD)) {
- if (mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()) {
- brightnessState = mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(
- mTempBrightnessEvent);
- if (BrightnessUtils.isValidBrightnessValue(brightnessState)
- || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT) {
- rawBrightnessState = mAutomaticBrightnessController
- .getRawAutomaticScreenBrightness();
- brightnessState = clampScreenBrightness(brightnessState);
- // slowly adapt to auto-brightness
- // TODO(b/253226419): slowChange should be decided by strategy.updateBrightness
- slowChange = mAutomaticBrightnessStrategy.hasAppliedAutoBrightness()
- && !mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged();
- brightnessAdjustmentFlags =
- mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags();
- updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState;
- mAutomaticBrightnessStrategy.setAutoBrightnessApplied(true);
- mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
- if (mScreenOffBrightnessSensorController != null) {
- mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
+
+ if (!mFlags.isRefactorDisplayPowerControllerEnabled()) {
+ // AutomaticBrightnessStrategy has higher priority than OffloadBrightnessStrategy
+ if (Float.isNaN(brightnessState)
+ || mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_OFFLOAD) {
+ if (mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()) {
+ brightnessState = mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(
+ mTempBrightnessEvent);
+ if (BrightnessUtils.isValidBrightnessValue(brightnessState)
+ || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT) {
+ rawBrightnessState = mAutomaticBrightnessController
+ .getRawAutomaticScreenBrightness();
+ // slowly adapt to auto-brightness
+ // TODO(b/253226419): slowChange should be decided by
+ // strategy.updateBrightness
+ slowChange = mAutomaticBrightnessStrategy.hasAppliedAutoBrightness()
+ && !mAutomaticBrightnessStrategy
+ .getAutoBrightnessAdjustmentChanged();
+ brightnessAdjustmentFlags =
+ mAutomaticBrightnessStrategy
+ .getAutoBrightnessAdjustmentReasonsFlags();
+ updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState;
+ mAutomaticBrightnessStrategy.setAutoBrightnessApplied(true);
+ mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
+ if (mScreenOffBrightnessSensorController != null) {
+ mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
+ }
+ setBrightnessFromOffload(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ } else {
+ mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
+ // Restore the lower-priority brightness strategy
+ brightnessState = displayBrightnessState.getBrightness();
}
- setBrightnessFromOffload(PowerManager.BRIGHTNESS_INVALID_FLOAT);
- } else {
- mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
- // Restore the lower-priority brightness strategy
- brightnessState = displayBrightnessState.getBrightness();
}
+ } else {
+ mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
}
- } else {
- // Any non-auto-brightness values such as override or temporary should still be subject
- // to clamping so that they don't go beyond the current max as specified by Brightness
- // Range Controller.
+ }
+
+ if (!Float.isNaN(brightnessState)) {
brightnessState = clampScreenBrightness(brightnessState);
- mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
}
// If there's an offload session, we need to set the initial doze brightness before
diff --git a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
index 3c2918f833ba..b24caf4ced76 100644
--- a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
+++ b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
@@ -91,8 +91,6 @@ class ExternalDisplayPolicy {
@NonNull
ExternalDisplayStatsService getExternalDisplayStatsService();
-
- void onExternalDisplayReadyToBeEnabled(int displayId);
}
@NonNull
@@ -187,10 +185,6 @@ class ExternalDisplayPolicy {
return;
}
- if (enabled) {
- mInjector.onExternalDisplayReadyToBeEnabled(logicalDisplay.getDisplayIdLocked());
- }
-
mLogicalDisplayMapper.setDisplayEnabledLocked(logicalDisplay, enabled);
}
@@ -223,7 +217,6 @@ class ExternalDisplayPolicy {
if ((Build.IS_ENG || Build.IS_USERDEBUG)
&& SystemProperties.getBoolean(ENABLE_ON_CONNECT, false)) {
Slog.w(TAG, "External display is enabled by default, bypassing user consent.");
- mInjector.onExternalDisplayReadyToBeEnabled(logicalDisplay.getDisplayIdLocked());
mInjector.sendExternalDisplayEventLocked(logicalDisplay, EVENT_DISPLAY_CONNECTED);
return;
} else {
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index ab0b3cbee3ad..fa423162985e 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -700,13 +700,6 @@ public class DisplayModeDirector {
}
/**
- * Called when external display is ready to be enabled.
- */
- public void onExternalDisplayReadyToBeEnabled(int displayId) {
- mDisplayObserver.onExternalDisplayReadyToBeEnabled(displayId);
- }
-
- /**
* Provides access to DisplayDeviceConfig for specific display
*/
public interface DisplayDeviceConfigProvider {
@@ -1110,20 +1103,6 @@ public class DisplayModeDirector {
if (Float.isInfinite(minRefreshRate)) {
// Infinity means that we want the highest possible refresh rate
minRefreshRate = highestRefreshRate;
-
- if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
- && displayId == Display.DEFAULT_DISPLAY) {
- // The flag has been turned off, we need to restore the original value. We'll
- // use the peak refresh rate of the default display.
- Settings.System.putFloatForUser(cr, Settings.System.MIN_REFRESH_RATE,
- highestRefreshRate, cr.getUserId());
- }
- } else if (mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
- && displayId == Display.DEFAULT_DISPLAY
- && Math.round(minRefreshRate) == Math.round(highestRefreshRate)) {
- // The flag has been turned on, we need to upgrade the setting
- Settings.System.putFloatForUser(cr, Settings.System.MIN_REFRESH_RATE,
- Float.POSITIVE_INFINITY, cr.getUserId());
}
float peakRefreshRate = Settings.System.getFloatForUser(cr,
@@ -1131,20 +1110,6 @@ public class DisplayModeDirector {
if (Float.isInfinite(peakRefreshRate)) {
// Infinity means that we want the highest possible refresh rate
peakRefreshRate = highestRefreshRate;
-
- if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
- && displayId == Display.DEFAULT_DISPLAY) {
- // The flag has been turned off, we need to restore the original value. We'll
- // use the peak refresh rate of the default display.
- Settings.System.putFloatForUser(cr, Settings.System.PEAK_REFRESH_RATE,
- highestRefreshRate, cr.getUserId());
- }
- } else if (mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
- && displayId == Display.DEFAULT_DISPLAY
- && Math.round(peakRefreshRate) == Math.round(highestRefreshRate)) {
- // The flag has been turned on, we need to upgrade the setting
- Settings.System.putFloatForUser(cr, Settings.System.PEAK_REFRESH_RATE,
- Float.POSITIVE_INFINITY, cr.getUserId());
}
updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate, mDefaultRefreshRate,
@@ -1400,13 +1365,6 @@ public class DisplayModeDirector {
}
}
-
- void onExternalDisplayReadyToBeEnabled(int displayId) {
- DisplayInfo displayInfo = getDisplayInfo(displayId);
- updateDisplaysPeakRefreshRateAndResolution(displayInfo);
- addDisplaysSynchronizedPeakRefreshRate(displayInfo);
- }
-
@Override
public void onDisplayAdded(int displayId) {
updateDisplayDeviceConfig(displayId);
@@ -1414,6 +1372,8 @@ public class DisplayModeDirector {
updateDisplayModes(displayId, displayInfo);
updateLayoutLimitedFrameRate(displayId, displayInfo);
updateUserSettingDisplayPreferredSize(displayInfo);
+ updateDisplaysPeakRefreshRateAndResolution(displayInfo);
+ addDisplaysSynchronizedPeakRefreshRate(displayInfo);
}
@Override
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 2583d73046ce..def6d6ca640a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1099,7 +1099,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
imesToClearAdditionalSubtypes.add(imiId);
}
int change = isPackageDisappearing(imi.getPackageName());
- if (change == PACKAGE_TEMPORARY_CHANGE || change == PACKAGE_PERMANENT_CHANGE) {
+ if (change == PACKAGE_PERMANENT_CHANGE) {
Slog.i(TAG, "Input method uninstalled, disabling: " + imi.getComponent());
if (isCurrentUser) {
setInputMethodEnabledLocked(imi.getId(), false);
@@ -5856,6 +5856,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
@SuppressWarnings("GuardedBy") Consumer<ClientState> clientControllerDump = c -> {
p.println(" " + c + ":");
p.println(" client=" + c.mClient);
+
p.println(" fallbackInputConnection="
+ c.mFallbackInputConnection);
p.println(" sessionRequested="
@@ -5864,6 +5865,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
" sessionRequestedForAccessibility="
+ c.mSessionRequestedForAccessibility);
p.println(" curSession=" + c.mCurSession);
+ p.println(" selfReportedDisplayId=" + c.mSelfReportedDisplayId);
+ p.println(" uid=" + c.mUid);
+ p.println(" pid=" + c.mPid);
};
mClientController.forAllClients(clientControllerDump);
p.println(" mCurrentUserId=" + mCurrentUserId);
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index 71a9f54c0b1c..32a0ef4c6a42 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -38,6 +38,7 @@ import android.telephony.TelephonyManager;
import android.util.Log;
import com.android.internal.location.GpsNetInitiatedHandler;
+import com.android.server.FgThread;
import java.net.Inet4Address;
import java.net.Inet6Address;
@@ -200,7 +201,12 @@ class GnssNetworkConnectivityHandler {
SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class);
if (subManager != null) {
- subManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangeListener);
+ if (Flags.subscriptionsListenerThread()) {
+ subManager.addOnSubscriptionsChangedListener(FgThread.getExecutor(),
+ mOnSubscriptionsChangeListener);
+ } else {
+ subManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangeListener);
+ }
}
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 8df38a8d565a..c105b9c37026 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -473,6 +473,10 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
.setProviderId(mUniqueId)
.setSystemSession(true)
.addSelectedRoute(MediaRoute2Info.ROUTE_ID_DEFAULT)
+ .setTransferReason(newSessionInfo.getTransferReason())
+ .setTransferInitiator(
+ newSessionInfo.getTransferInitiatorUserHandle(),
+ newSessionInfo.getTransferInitiatorPackageName())
.build();
return true;
}
diff --git a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
index 5ebcca875d86..2c1453261808 100644
--- a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
@@ -275,10 +275,7 @@ public class PersistentDataBlockService extends SystemService {
if (mFrpEnforced) {
automaticallyDeactivateFrpIfPossible();
setOemUnlockEnabledProperty(doGetOemUnlockEnabled());
- // Set the SECURE_FRP_MODE flag, for backward compatibility with clients who use it.
- // They should switch to calling #isFrpActive().
- Settings.Global.putInt(mContext.getContentResolver(),
- Settings.Global.SECURE_FRP_MODE, mFrpActive ? 1 : 0);
+ setOldSettingForBackworkCompatibility(mFrpActive);
} else {
formatIfOemUnlockEnabled();
}
@@ -292,6 +289,13 @@ public class PersistentDataBlockService extends SystemService {
mInitDoneSignal.countDown();
}
+ private void setOldSettingForBackworkCompatibility(boolean isActive) {
+ // Set the SECURE_FRP_MODE flag, for backward compatibility with clients who use it.
+ // They should switch to calling #isFrpActive().
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.SECURE_FRP_MODE, isActive ? 1 : 0);
+ }
+
private void setOemUnlockEnabledProperty(boolean oemUnlockEnabled) {
setProperty(OEM_UNLOCK_PROP, oemUnlockEnabled ? "1" : "0");
}
@@ -628,6 +632,7 @@ public class PersistentDataBlockService extends SystemService {
Slog.w(TAG, "Upgrading from Android 14 or lower, defaulting FRP secret");
writeFrpMagicAndDefaultSecret();
mFrpActive = false;
+ setOldSettingForBackworkCompatibility(mFrpActive);
return true;
}
@@ -699,6 +704,7 @@ public class PersistentDataBlockService extends SystemService {
void activateFrp() {
synchronized (mLock) {
mFrpActive = true;
+ setOldSettingForBackworkCompatibility(mFrpActive);
}
}
@@ -740,6 +746,7 @@ public class PersistentDataBlockService extends SystemService {
if (MessageDigest.isEqual(secret, partitionSecret)) {
mFrpActive = false;
Slog.i(TAG, "FRP secret matched, FRP deactivated.");
+ setOldSettingForBackworkCompatibility(mFrpActive);
return true;
} else {
Slog.e(TAG,
@@ -1315,6 +1322,7 @@ public class PersistentDataBlockService extends SystemService {
public boolean deactivateFactoryResetProtectionWithoutSecret() {
synchronized (mLock) {
mFrpActive = false;
+ setOldSettingForBackworkCompatibility(/* isActive */ mFrpActive);
}
return true;
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index bd0501d920c4..20c5b5f8a308 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -462,6 +462,11 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
@Override
+ public int getNumRegisteredAttributionSources(int uid) {
+ return mAttributionSourceRegistry.getNumRegisteredAttributionSources(uid);
+ }
+
+ @Override
public List<String> getAutoRevokeExemptionRequestedPackages(int userId) {
return getPackagesWithAutoRevokePolicy(AUTO_REVOKE_DISCOURAGED, userId);
}
@@ -938,6 +943,26 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
}
+ public int getNumRegisteredAttributionSources(int uid) {
+ mContext.enforceCallingOrSelfPermission(UPDATE_APP_OPS_STATS,
+ "getting the number of registered AttributionSources requires "
+ + "UPDATE_APP_OPS_STATS");
+ // Influence the system to perform a garbage collection, so the provided number is as
+ // accurate as possible
+ System.gc();
+ System.gc();
+ synchronized (mLock) {
+ int[] numForUid = { 0 };
+ mAttributions.forEach((key, value) -> {
+ if (value.getUid() == uid) {
+ numForUid[0]++;
+ }
+
+ });
+ return numForUid[0];
+ }
+ }
+
private int resolveUid(int uid) {
final VoiceInteractionManagerInternal vimi = LocalServices
.getService(VoiceInteractionManagerInternal.class);
diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 93f26aefb692..93f26aefb692 100644
--- a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
index 519c0edfc532..519c0edfc532 100644
--- a/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java
+++ b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
diff --git a/services/core/java/com/android/server/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java
index 5b501e16d652..587be0746149 100644
--- a/services/core/java/com/android/server/security/FileIntegrityService.java
+++ b/services/core/java/com/android/server/security/FileIntegrityService.java
@@ -16,6 +16,7 @@
package com.android.server.security;
+import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
@@ -27,6 +28,7 @@ import android.os.Build;
import android.os.Environment;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
+import android.os.PermissionEnforcer;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
@@ -79,7 +81,11 @@ public class FileIntegrityService extends SystemService {
return LocalServices.getService(FileIntegrityService.class);
}
- private final IBinder mService = new IFileIntegrityService.Stub() {
+ private final class BinderService extends IFileIntegrityService.Stub {
+ BinderService(Context context) {
+ super(PermissionEnforcer.fromContext(context));
+ }
+
@Override
public boolean isApkVeritySupported() {
return VerityUtils.isFsVeritySupported();
@@ -168,12 +174,10 @@ public class FileIntegrityService extends SystemService {
}
@Override
+ @EnforcePermission(android.Manifest.permission.SETUP_FSVERITY)
public int setupFsverity(android.os.IInstalld.IFsveritySetupAuthToken authToken,
String filePath, String packageName) throws RemoteException {
- getContext().enforceCallingPermission(android.Manifest.permission.SETUP_FSVERITY,
- "Permission android.permission.SETUP_FSVERITY not grantted to access "
- + "FileIntegrityManager#setupFsverity");
-
+ setupFsverity_enforcePermission();
Objects.requireNonNull(authToken);
Objects.requireNonNull(filePath);
Objects.requireNonNull(packageName);
@@ -185,10 +189,12 @@ public class FileIntegrityService extends SystemService {
throw new RemoteException(e);
}
}
- };
+ }
+ private final IBinder mService;
public FileIntegrityService(final Context context) {
super(context);
+ mService = new BinderService(context);
try {
sCertFactory = CertificateFactory.getInstance("X.509");
} catch (CertificateException e) {
diff --git a/services/core/java/com/android/server/selinux/QuotaLimiter.java b/services/core/java/com/android/server/selinux/QuotaLimiter.java
index e89ddfd2627c..34d18cecf909 100644
--- a/services/core/java/com/android/server/selinux/QuotaLimiter.java
+++ b/services/core/java/com/android/server/selinux/QuotaLimiter.java
@@ -34,10 +34,10 @@ public class QuotaLimiter {
private final Clock mClock;
private final Duration mWindowSize;
- private final int mMaxPermits;
- private long mCurrentWindow = 0;
- private int mPermitsGranted = 0;
+ private int mMaxPermits;
+ private long mCurrentWindow;
+ private int mPermitsGranted;
@VisibleForTesting
QuotaLimiter(Clock clock, Duration windowSize, int maxPermits) {
@@ -75,4 +75,8 @@ public class QuotaLimiter {
return false;
}
+
+ public void setMaxPermits(int maxPermits) {
+ this.mMaxPermits = maxPermits;
+ }
}
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java
index 8d8d5960038e..d69150d88e4f 100644
--- a/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java
@@ -15,35 +15,66 @@
*/
package com.android.server.selinux;
+import android.provider.DeviceConfig;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.Arrays;
import java.util.Iterator;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
import java.util.stream.Stream;
/** Builder for SelinuxAuditLogs. */
class SelinuxAuditLogBuilder {
- // Currently logs collection is hardcoded for the sdk_sandbox_audit.
- private static final String SDK_SANDBOX_AUDIT = "sdk_sandbox_audit";
- static final Matcher SCONTEXT_MATCHER =
- Pattern.compile(
- "u:r:(?<stype>"
- + SDK_SANDBOX_AUDIT
- + "):s0(:c)?(?<scategories>((,c)?\\d+)+)*")
- .matcher("");
+ private static final String TAG = "SelinuxAuditLogs";
- static final Matcher TCONTEXT_MATCHER =
- Pattern.compile("u:object_r:(?<ttype>\\w+):s0(:c)?(?<tcategories>((,c)?\\d+)+)*")
- .matcher("");
+ // This config indicates which Selinux logs for source domains to collect. The string will be
+ // inserted into a regex, so it must follow the regex syntax. For example, a valid value would
+ // be "system_server|untrusted_app".
+ @VisibleForTesting static final String CONFIG_SELINUX_AUDIT_DOMAIN = "selinux_audit_domain";
+ private static final Matcher NO_OP_MATCHER = Pattern.compile("no-op^").matcher("");
+ private static final String TCONTEXT_PATTERN =
+ "u:object_r:(?<ttype>\\w+):s0(:c)?(?<tcategories>((,c)?\\d+)+)*";
+ private static final String PATH_PATTERN = "\"(?<path>/\\w+(/\\w+)?)(/\\w+)*\"";
- static final Matcher PATH_MATCHER =
- Pattern.compile("\"(?<path>/\\w+(/\\w+)?)(/\\w+)*\"").matcher("");
+ @VisibleForTesting final Matcher mScontextMatcher;
+ @VisibleForTesting final Matcher mTcontextMatcher;
+ @VisibleForTesting final Matcher mPathMatcher;
private Iterator<String> mTokens;
private final SelinuxAuditLog mAuditLog = new SelinuxAuditLog();
+ SelinuxAuditLogBuilder() {
+ Matcher scontextMatcher = NO_OP_MATCHER;
+ Matcher tcontextMatcher = NO_OP_MATCHER;
+ Matcher pathMatcher = NO_OP_MATCHER;
+ try {
+ scontextMatcher =
+ Pattern.compile(
+ TextUtils.formatSimple(
+ "u:r:(?<stype>%s):s0(:c)?(?<scategories>((,c)?\\d+)+)*",
+ DeviceConfig.getString(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ CONFIG_SELINUX_AUDIT_DOMAIN,
+ "no_match^")))
+ .matcher("");
+ tcontextMatcher = Pattern.compile(TCONTEXT_PATTERN).matcher("");
+ pathMatcher = Pattern.compile(PATH_PATTERN).matcher("");
+ } catch (PatternSyntaxException e) {
+ Slog.e(TAG, "Invalid pattern, setting every matcher to no-op.", e);
+ }
+
+ mScontextMatcher = scontextMatcher;
+ mTcontextMatcher = tcontextMatcher;
+ mPathMatcher = pathMatcher;
+ }
+
void reset(String denialString) {
mTokens =
Arrays.asList(
@@ -82,18 +113,18 @@ class SelinuxAuditLogBuilder {
mAuditLog.mPermissions = permissionsStream.build().toArray(String[]::new);
break;
case "scontext":
- if (!nextTokenMatches(SCONTEXT_MATCHER)) {
+ if (!nextTokenMatches(mScontextMatcher)) {
return null;
}
- mAuditLog.mSType = SCONTEXT_MATCHER.group("stype");
- mAuditLog.mSCategories = toCategories(SCONTEXT_MATCHER.group("scategories"));
+ mAuditLog.mSType = mScontextMatcher.group("stype");
+ mAuditLog.mSCategories = toCategories(mScontextMatcher.group("scategories"));
break;
case "tcontext":
- if (!nextTokenMatches(TCONTEXT_MATCHER)) {
+ if (!nextTokenMatches(mTcontextMatcher)) {
return null;
}
- mAuditLog.mTType = TCONTEXT_MATCHER.group("ttype");
- mAuditLog.mTCategories = toCategories(TCONTEXT_MATCHER.group("tcategories"));
+ mAuditLog.mTType = mTcontextMatcher.group("ttype");
+ mAuditLog.mTCategories = toCategories(mTcontextMatcher.group("tcategories"));
break;
case "tclass":
if (!mTokens.hasNext()) {
@@ -102,8 +133,8 @@ class SelinuxAuditLogBuilder {
mAuditLog.mTClass = mTokens.next();
break;
case "path":
- if (nextTokenMatches(PATH_MATCHER)) {
- mAuditLog.mPath = PATH_MATCHER.group("path");
+ if (nextTokenMatches(mPathMatcher)) {
+ mAuditLog.mPath = mPathMatcher.group("path");
}
break;
case "permissive":
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java
index 03822aaf76b2..c655d46eb9f4 100644
--- a/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java
@@ -18,10 +18,12 @@ package com.android.server.selinux;
import android.util.EventLog;
import android.util.EventLog.Event;
import android.util.Log;
+import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.selinux.SelinuxAuditLogBuilder.SelinuxAuditLog;
+import com.android.server.utils.Slogf;
import java.io.IOException;
import java.time.Instant;
@@ -37,6 +39,7 @@ import java.util.regex.Pattern;
class SelinuxAuditLogsCollector {
private static final String TAG = "SelinuxAuditLogs";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final String SELINUX_PATTERN = "^.*\\bavc:\\s+(?<denial>.*)$";
@@ -48,13 +51,17 @@ class SelinuxAuditLogsCollector {
@VisibleForTesting Instant mLastWrite = Instant.MIN;
- final AtomicBoolean mStopRequested = new AtomicBoolean(false);
+ AtomicBoolean mStopRequested = new AtomicBoolean(false);
SelinuxAuditLogsCollector(RateLimiter rateLimiter, QuotaLimiter quotaLimiter) {
mRateLimiter = rateLimiter;
mQuotaLimiter = quotaLimiter;
}
+ public void setStopRequested(boolean stopRequested) {
+ mStopRequested.set(stopRequested);
+ }
+
/**
* Collect and push SELinux audit logs for the provided {@code tagCode}.
*
@@ -66,7 +73,7 @@ class SelinuxAuditLogsCollector {
boolean quotaExceeded = writeAuditLogs(logLines);
if (quotaExceeded) {
- Log.w(TAG, "Too many SELinux logs in the queue, I am giving up.");
+ Slog.w(TAG, "Too many SELinux logs in the queue, I am giving up.");
mLastWrite = latestTimestamp; // next run we will ignore all these logs.
logLines.clear();
}
@@ -79,7 +86,7 @@ class SelinuxAuditLogsCollector {
try {
EventLog.readEvents(new int[] {tagCode}, events);
} catch (IOException e) {
- Log.e(TAG, "Error reading event logs", e);
+ Slog.e(TAG, "Error reading event logs", e);
}
Instant latestTimestamp = mLastWrite;
@@ -102,6 +109,7 @@ class SelinuxAuditLogsCollector {
private boolean writeAuditLogs(Queue<Event> logLines) {
final SelinuxAuditLogBuilder auditLogBuilder = new SelinuxAuditLogBuilder();
+ int auditsWritten = 0;
while (!mStopRequested.get() && !logLines.isEmpty()) {
Event event = logLines.poll();
@@ -118,6 +126,9 @@ class SelinuxAuditLogsCollector {
}
if (!mQuotaLimiter.acquire()) {
+ if (DEBUG) {
+ Slogf.d(TAG, "Running out of quota after %d logs.", auditsWritten);
+ }
return true;
}
mRateLimiter.acquire();
@@ -133,12 +144,16 @@ class SelinuxAuditLogsCollector {
auditLog.mTClass,
auditLog.mPath,
auditLog.mPermissive);
+ auditsWritten++;
if (logTime.isAfter(mLastWrite)) {
mLastWrite = logTime;
}
}
+ if (DEBUG) {
+ Slogf.d(TAG, "Written %d logs", auditsWritten);
+ }
return false;
}
}
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsJob.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsJob.java
new file mode 100644
index 000000000000..0092c3797156
--- /dev/null
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsJob.java
@@ -0,0 +1,60 @@
+/*
+ * 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.selinux;
+
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+import android.util.Slog;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * This class handles the start and stop requests for the logs collector job, in particular making
+ * sure that at most one job is running at any given moment.
+ */
+final class SelinuxAuditLogsJob {
+
+ private static final String TAG = "SelinuxAuditLogs";
+
+ private final AtomicBoolean mIsRunning = new AtomicBoolean(false);
+ private final SelinuxAuditLogsCollector mAuditLogsCollector;
+
+ SelinuxAuditLogsJob(SelinuxAuditLogsCollector auditLogsCollector) {
+ mAuditLogsCollector = auditLogsCollector;
+ }
+
+ void requestStop() {
+ mAuditLogsCollector.mStopRequested.set(true);
+ }
+
+ boolean isRunning() {
+ return mIsRunning.get();
+ }
+
+ public void start(JobService jobService, JobParameters params) {
+ mAuditLogsCollector.mStopRequested.set(false);
+ if (mIsRunning.get()) {
+ Slog.i(TAG, "Selinux audit job is already running, ignore start request.");
+ return;
+ }
+ mIsRunning.set(true);
+ boolean done = mAuditLogsCollector.collect(SelinuxAuditLogsService.AUDITD_TAG_CODE);
+ if (done) {
+ jobService.jobFinished(params, /* wantsReschedule= */ false);
+ }
+ mIsRunning.set(false);
+ }
+}
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java
index 8a661bcc13af..d46e8916d9e9 100644
--- a/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java
@@ -23,14 +23,16 @@ import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.Properties;
import android.util.EventLog;
-import android.util.Log;
+import android.util.Slog;
import java.time.Duration;
+import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
/**
* Scheduled jobs related to logging of SELinux denials and audits. The job runs daily on idle
@@ -43,58 +45,68 @@ public class SelinuxAuditLogsService extends JobService {
static final int AUDITD_TAG_CODE = EventLog.getTagCode("auditd");
+ private static final String CONFIG_SELINUX_AUDIT_JOB_FREQUENCY_HOURS =
+ "selinux_audit_job_frequency_hours";
+ private static final String CONFIG_SELINUX_ENABLE_AUDIT_JOB = "selinux_enable_audit_job";
+ private static final String CONFIG_SELINUX_AUDIT_CAP = "selinux_audit_cap";
+ private static final int MAX_PERMITS_CAP_DEFAULT = 50000;
+
private static final int SELINUX_AUDIT_JOB_ID = 25327386;
- private static final JobInfo SELINUX_AUDIT_JOB =
- new JobInfo.Builder(
- SELINUX_AUDIT_JOB_ID,
- new ComponentName("android", SelinuxAuditLogsService.class.getName()))
- .setPeriodic(TimeUnit.DAYS.toMillis(1))
- .setRequiresDeviceIdle(true)
- .setRequiresCharging(true)
- .setRequiresBatteryNotLow(true)
- .build();
+ private static final ComponentName SELINUX_AUDIT_JOB_COMPONENT =
+ new ComponentName("android", SelinuxAuditLogsService.class.getName());
private static final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor();
- private static final AtomicReference<Boolean> IS_RUNNING = new AtomicReference<>(false);
- // Audit logging is subject to both rate and quota limiting. We can only push one atom every 10
- // milliseconds, and no more than 50K atoms can be pushed each day.
- private static final SelinuxAuditLogsCollector AUDIT_LOGS_COLLECTOR =
- new SelinuxAuditLogsCollector(
- new RateLimiter(/* window= */ Duration.ofMillis(10)),
- new QuotaLimiter(/* maxPermitsPerDay= */ 50000));
+ // Audit logging is subject to both rate and quota limiting. A {@link RateLimiter} makes sure
+ // that we push no more than one atom every 10 milliseconds. A {@link QuotaLimiter} caps the
+ // number of atoms pushed per day to CONFIG_SELINUX_AUDIT_CAP. The quota limiter is static
+ // because new job executions happen in a new instance of this class. Making the quota limiter
+ // an instance reference would reset the quota limitations between jobs executions.
+ private static final Duration RATE_LIMITER_WINDOW = Duration.ofMillis(10);
+ private static final QuotaLimiter QUOTA_LIMITER =
+ new QuotaLimiter(
+ DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ CONFIG_SELINUX_AUDIT_CAP,
+ MAX_PERMITS_CAP_DEFAULT));
+ private static final SelinuxAuditLogsJob LOGS_COLLECTOR_JOB =
+ new SelinuxAuditLogsJob(
+ new SelinuxAuditLogsCollector(
+ new RateLimiter(RATE_LIMITER_WINDOW), QUOTA_LIMITER));
/** Schedule jobs with the {@link JobScheduler}. */
public static void schedule(Context context) {
if (!selinuxSdkSandboxAudit()) {
- Log.d(TAG, "SelinuxAuditLogsService not enabled");
+ Slog.d(TAG, "SelinuxAuditLogsService not enabled");
return;
}
if (AUDITD_TAG_CODE == -1) {
- Log.e(TAG, "auditd is not a registered tag on this system");
+ Slog.e(TAG, "auditd is not a registered tag on this system");
return;
}
- if (context.getSystemService(JobScheduler.class)
- .forNamespace(SELINUX_AUDIT_NAMESPACE)
- .schedule(SELINUX_AUDIT_JOB)
- == JobScheduler.RESULT_FAILURE) {
- Log.e(TAG, "SelinuxAuditLogsService could not be started.");
- }
+ LogsCollectorJobScheduler propertiesListener =
+ new LogsCollectorJobScheduler(
+ context.getSystemService(JobScheduler.class)
+ .forNamespace(SELINUX_AUDIT_NAMESPACE));
+ propertiesListener.schedule();
+ DeviceConfig.addOnPropertiesChangedListener(
+ DeviceConfig.NAMESPACE_ADSERVICES, context.getMainExecutor(), propertiesListener);
}
@Override
public boolean onStartJob(JobParameters params) {
if (params.getJobId() != SELINUX_AUDIT_JOB_ID) {
- Log.e(TAG, "The job id does not match the expected selinux job id.");
+ Slog.e(TAG, "The job id does not match the expected selinux job id.");
+ return false;
+ }
+ if (!selinuxSdkSandboxAudit()) {
+ Slog.i(TAG, "Selinux audit job disabled.");
return false;
}
- AUDIT_LOGS_COLLECTOR.mStopRequested.set(false);
- IS_RUNNING.set(true);
- EXECUTOR_SERVICE.execute(new LogsCollectorJob(this, params));
-
+ EXECUTOR_SERVICE.execute(() -> LOGS_COLLECTOR_JOB.start(this, params));
return true; // the job is running
}
@@ -104,29 +116,69 @@ public class SelinuxAuditLogsService extends JobService {
return false;
}
- AUDIT_LOGS_COLLECTOR.mStopRequested.set(true);
- return IS_RUNNING.get();
+ if (LOGS_COLLECTOR_JOB.isRunning()) {
+ LOGS_COLLECTOR_JOB.requestStop();
+ return true;
+ }
+ return false;
}
- private static class LogsCollectorJob implements Runnable {
- private final JobService mAuditLogService;
- private final JobParameters mParams;
+ /**
+ * This class is in charge of scheduling the job service, and keeping the scheduling up to date
+ * when the parameters change.
+ */
+ private static final class LogsCollectorJobScheduler
+ implements DeviceConfig.OnPropertiesChangedListener {
+
+ private final JobScheduler mJobScheduler;
- LogsCollectorJob(JobService auditLogService, JobParameters params) {
- mAuditLogService = auditLogService;
- mParams = params;
+ private LogsCollectorJobScheduler(JobScheduler jobScheduler) {
+ mJobScheduler = jobScheduler;
}
@Override
- public void run() {
- IS_RUNNING.updateAndGet(
- isRunning -> {
- boolean done = AUDIT_LOGS_COLLECTOR.collect(AUDITD_TAG_CODE);
- if (done) {
- mAuditLogService.jobFinished(mParams, /* wantsReschedule= */ false);
- }
- return !done;
- });
+ public void onPropertiesChanged(Properties changedProperties) {
+ Set<String> keyset = changedProperties.getKeyset();
+
+ if (keyset.contains(CONFIG_SELINUX_AUDIT_CAP)) {
+ QUOTA_LIMITER.setMaxPermits(
+ changedProperties.getInt(
+ CONFIG_SELINUX_AUDIT_CAP, MAX_PERMITS_CAP_DEFAULT));
+ }
+
+ if (keyset.contains(CONFIG_SELINUX_ENABLE_AUDIT_JOB)) {
+ boolean enabled =
+ changedProperties.getBoolean(
+ CONFIG_SELINUX_ENABLE_AUDIT_JOB, /* defaultValue= */ false);
+ if (enabled) {
+ schedule();
+ } else {
+ mJobScheduler.cancel(SELINUX_AUDIT_JOB_ID);
+ }
+ } else if (keyset.contains(CONFIG_SELINUX_AUDIT_JOB_FREQUENCY_HOURS)) {
+ // The job frequency changed, reschedule.
+ schedule();
+ }
+ }
+
+ private void schedule() {
+ long frequencyMillis =
+ TimeUnit.HOURS.toMillis(
+ DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ CONFIG_SELINUX_AUDIT_JOB_FREQUENCY_HOURS,
+ 24));
+ if (mJobScheduler.schedule(
+ new JobInfo.Builder(SELINUX_AUDIT_JOB_ID, SELINUX_AUDIT_JOB_COMPONENT)
+ .setPeriodic(frequencyMillis)
+ .setRequiresDeviceIdle(true)
+ .setRequiresBatteryNotLow(true)
+ .build())
+ == JobScheduler.RESULT_FAILURE) {
+ Slog.e(TAG, "SelinuxAuditLogsService could not be scheduled.");
+ } else {
+ Slog.d(TAG, "SelinuxAuditLogsService scheduled successfully.");
+ }
}
}
}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index b5df30f05947..e0247d0ceb47 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -61,6 +61,7 @@ import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STA
import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__UNKNOWN;
import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
import static com.android.server.stats.Flags.addMobileBytesTransferByProcStatePuller;
+import static com.android.server.stats.Flags.statsPullNetworkStatsManagerInitOrderFix;
import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
import static com.android.server.stats.pull.ProcfsMemoryUtil.getProcessCmdlines;
@@ -355,7 +356,17 @@ public class StatsPullAtomService extends SystemService {
private TelephonyManager mTelephony;
private UwbManager mUwbManager;
private SubscriptionManager mSubscriptionManager;
- private NetworkStatsManager mNetworkStatsManager;
+
+ /**
+ * NetworkStatsManager initialization happens from one thread before any worker thread
+ * is going to access the networkStatsManager instance:
+ * - @initNetworkStatsManager() - initialization happens no worker thread to access are
+ * active yet
+ * - @initAndRegisterNetworkStatsPullers Network stats dependant pullers can only be
+ * initialized after service is ready. Worker thread is spawn here only after the
+ * initialization is completed in a thread safe way (no async access expected)
+ */
+ private NetworkStatsManager mNetworkStatsManager = null;
@GuardedBy("mKernelWakelockLock")
private KernelWakelockReader mKernelWakelockReader;
@@ -420,6 +431,12 @@ public class StatsPullAtomService extends SystemService {
public static final boolean ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER =
addMobileBytesTransferByProcStatePuller();
+ /**
+ * Whether or not to enable the mNetworkStatsManager initialization order fix
+ */
+ private static final boolean ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX =
+ statsPullNetworkStatsManagerInitOrderFix();
+
// Puller locks
private final Object mDataBytesTransferLock = new Object();
private final Object mBluetoothBytesTransferLock = new Object();
@@ -824,6 +841,9 @@ public class StatsPullAtomService extends SystemService {
});
} else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
BackgroundThread.getHandler().post(() -> {
+ if (ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX) {
+ initNetworkStatsManager();
+ }
// Network stats related pullers can only be initialized after service is ready.
initAndRegisterNetworkStatsPullers();
// For services that are not ready at boot phase PHASE_SYSTEM_SERVICES_READY
@@ -843,7 +863,9 @@ public class StatsPullAtomService extends SystemService {
mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
mStatsSubscriptionsListener = new StatsSubscriptionsListener(mSubscriptionManager);
mStorageManager = (StorageManager) mContext.getSystemService(StorageManager.class);
- mNetworkStatsManager = mContext.getSystemService(NetworkStatsManager.class);
+ if (!ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX) {
+ initNetworkStatsManager();
+ }
// Initialize DiskIO
mStoragedUidIoStatsReader = new StoragedUidIoStatsReader();
@@ -1019,6 +1041,24 @@ public class StatsPullAtomService extends SystemService {
}
}
+ /**
+ * Calling getNetworkStatsManager() before PHASE_THIRD_PARTY_APPS_CAN_START is unexpected
+ * Callers use before PHASE_THIRD_PARTY_APPS_CAN_START stage is not legit
+ */
+ @NonNull
+ private NetworkStatsManager getNetworkStatsManager() {
+ if (ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX) {
+ if (mNetworkStatsManager == null) {
+ throw new IllegalStateException("NetworkStatsManager is not ready");
+ }
+ }
+ return mNetworkStatsManager;
+ }
+
+ private void initNetworkStatsManager() {
+ mNetworkStatsManager = mContext.getSystemService(NetworkStatsManager.class);
+ }
+
private void initAndRegisterNetworkStatsPullers() {
if (DEBUG) {
Slog.d(TAG, "Registering NetworkStats pullers with statsd");
@@ -1514,11 +1554,11 @@ public class StatsPullAtomService extends SystemService {
// I/O and also block main thread when polling.
// Consider making perfd queries NetworkStatsService directly.
if (template.getMatchRule() == MATCH_WIFI && template.getSubscriberIds().isEmpty()) {
- mNetworkStatsManager.forceUpdate();
+ getNetworkStatsManager().forceUpdate();
}
final android.app.usage.NetworkStats queryNonTaggedStats =
- mNetworkStatsManager.querySummary(
+ getNetworkStatsManager().querySummary(
template, currentTimeInMillis - elapsedMillisSinceBoot - bucketDuration,
currentTimeInMillis);
@@ -1528,7 +1568,7 @@ public class StatsPullAtomService extends SystemService {
if (!includeTags) return nonTaggedStats;
final android.app.usage.NetworkStats queryTaggedStats =
- mNetworkStatsManager.queryTaggedSummary(template,
+ getNetworkStatsManager().queryTaggedSummary(template,
currentTimeInMillis - elapsedMillisSinceBoot - bucketDuration,
currentTimeInMillis);
final NetworkStats taggedStats =
diff --git a/services/core/java/com/android/server/stats/stats_flags.aconfig b/services/core/java/com/android/server/stats/stats_flags.aconfig
index 101b98e1785d..c479c6d11164 100644
--- a/services/core/java/com/android/server/stats/stats_flags.aconfig
+++ b/services/core/java/com/android/server/stats/stats_flags.aconfig
@@ -7,4 +7,12 @@ flag {
description: "Adds mobile_bytes_transfer_by_proc_state atom with system server side aggregation"
bug: "309512867"
is_fixed_read_only: true
-} \ No newline at end of file
+}
+
+flag {
+ name: "stats_pull_network_stats_manager_init_order_fix"
+ namespace: "statsd"
+ description: "Fix the mNetworkStatsManager initialization order"
+ bug: "331989853"
+ is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java
index 4a81c95f0b8f..440d2514537c 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java
@@ -89,8 +89,34 @@ public class CasResource {
* @param ownerId the removing client id of the owner.
*/
public void removeOwner(int ownerId) {
- mAvailableSessionNum += mOwnerClientIdsToSessionNum.get(ownerId);
- mOwnerClientIdsToSessionNum.remove(ownerId);
+ if (mOwnerClientIdsToSessionNum.containsKey(ownerId)) {
+ mAvailableSessionNum += mOwnerClientIdsToSessionNum.get(ownerId);
+ mOwnerClientIdsToSessionNum.remove(ownerId);
+ }
+ }
+
+ /**
+ * Remove a single session from resource
+ *
+ * @param ownerId the client Id of the owner of the session
+ */
+ public void removeSession(int ownerId) {
+ if (mOwnerClientIdsToSessionNum.containsKey(ownerId)) {
+ int sessionNum = mOwnerClientIdsToSessionNum.get(ownerId);
+ if (sessionNum > 0) {
+ mOwnerClientIdsToSessionNum.put(ownerId, --sessionNum);
+ mAvailableSessionNum++;
+ }
+ }
+ }
+
+ /**
+ * Check if there are any open sessions owned by a client
+ *
+ * @param ownerId the client Id of the owner of the sessions
+ */
+ public boolean hasOpenSessions(int ownerId) {
+ return mOwnerClientIdsToSessionNum.get(ownerId) > 0;
}
public Set<Integer> getOwnerClientIds() {
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index cddc79db6106..0afb049d31c7 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -1924,11 +1924,13 @@ public class TunerResourceManagerService extends SystemService implements IBinde
ownerProfile.useCiCam(grantingId);
}
- private void updateCasClientMappingOnRelease(
- @NonNull CasResource releasingCas, int ownerClientId) {
- ClientProfile ownerProfile = getClientProfile(ownerClientId);
- releasingCas.removeOwner(ownerClientId);
- ownerProfile.releaseCas();
+ private void updateCasClientMappingOnRelease(@NonNull CasResource cas, int ownerClientId) {
+ cas.removeSession(ownerClientId);
+ if (!cas.hasOpenSessions(ownerClientId)) {
+ ClientProfile ownerProfile = getClientProfile(ownerClientId);
+ cas.removeOwner(ownerClientId);
+ ownerProfile.releaseCas();
+ }
}
private void updateCiCamClientMappingOnRelease(
diff --git a/services/core/java/com/android/server/utils/AnrTimer.java b/services/core/java/com/android/server/utils/AnrTimer.java
index b7d8cfce34ed..e944eca63144 100644
--- a/services/core/java/com/android/server/utils/AnrTimer.java
+++ b/services/core/java/com/android/server/utils/AnrTimer.java
@@ -193,6 +193,10 @@ public class AnrTimer<V> implements AutoCloseable {
@GuardedBy("mLock")
private int mTotalStarted = 0;
+ /** The total number of timers that were restarted without an explicit cancel. */
+ @GuardedBy("mLock")
+ private int mTotalRestarted = 0;
+
/** The total number of errors detected. */
@GuardedBy("mLock")
private int mTotalErrors = 0;
@@ -434,10 +438,10 @@ public class AnrTimer<V> implements AutoCloseable {
@Override
void start(@NonNull V arg, int pid, int uid, long timeoutMs) {
synchronized (mLock) {
- if (mTimerIdMap.containsKey(arg)) {
- // There is an existing timer. Cancel it.
- cancel(arg);
- }
+ // If there is an existing timer, cancel it. This is a nop if the timer does not
+ // exist.
+ if (cancel(arg)) mTotalRestarted++;
+
int timerId = nativeAnrTimerStart(mNative, pid, uid, timeoutMs, mExtend);
if (timerId > 0) {
mTimerIdMap.put(arg, timerId);
@@ -546,9 +550,7 @@ public class AnrTimer<V> implements AutoCloseable {
private Integer removeLocked(V arg) {
Integer r = mTimerIdMap.remove(arg);
if (r != null) {
- synchronized (mTimerArgMap) {
- mTimerArgMap.remove(r);
- }
+ mTimerArgMap.remove(r);
}
return r;
}
@@ -672,8 +674,8 @@ public class AnrTimer<V> implements AutoCloseable {
synchronized (mLock) {
pw.format("timer: %s\n", mLabel);
pw.increaseIndent();
- pw.format("started=%d maxStarted=%d running=%d expired=%d errors=%d\n",
- mTotalStarted, mMaxStarted, mTimerIdMap.size(),
+ pw.format("started=%d maxStarted=%d restarted=%d running=%d expired=%d errors=%d\n",
+ mTotalStarted, mMaxStarted, mTotalRestarted, mTimerIdMap.size(),
mTotalExpired, mTotalErrors);
pw.decreaseIndent();
mFeature.dump(pw, false);
diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java
index 1383708d8a5c..6a4c9c26d314 100644
--- a/services/core/java/com/android/server/vcn/VcnContext.java
+++ b/services/core/java/com/android/server/vcn/VcnContext.java
@@ -18,6 +18,7 @@ package com.android.server.vcn;
import android.annotation.NonNull;
import android.content.Context;
+import android.net.IpSecTransformState;
import android.net.vcn.FeatureFlags;
import android.net.vcn.FeatureFlagsImpl;
import android.os.Looper;
@@ -34,7 +35,6 @@ public class VcnContext {
@NonNull private final Looper mLooper;
@NonNull private final VcnNetworkProvider mVcnNetworkProvider;
@NonNull private final FeatureFlags mFeatureFlags;
- @NonNull private final android.net.platform.flags.FeatureFlags mCoreNetFeatureFlags;
private final boolean mIsInTestMode;
public VcnContext(
@@ -49,7 +49,6 @@ public class VcnContext {
// Auto-generated class
mFeatureFlags = new FeatureFlagsImpl();
- mCoreNetFeatureFlags = new android.net.platform.flags.FeatureFlagsImpl();
}
@NonNull
@@ -76,7 +75,16 @@ public class VcnContext {
}
public boolean isFlagIpSecTransformStateEnabled() {
- return mCoreNetFeatureFlags.ipsecTransformState();
+ // TODO: b/328844044: Ideally this code should gate the behavior by checking the
+ // android.net.platform.flags.ipsec_transform_state flag but that flag is not accessible
+ // right now. We should either update the code when the flag is accessible or remove the
+ // legacy behavior after VIC SDK finalization
+ try {
+ new IpSecTransformState.Builder();
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
}
@NonNull
diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java
index b33fa6f56a23..f82ff673d74e 100644
--- a/services/core/java/com/android/server/vibrator/VibratorControlService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java
@@ -101,7 +101,9 @@ final class VibratorControlService extends IVibratorControlService.Stub {
}
@Override
- public void registerVibratorController(IVibratorController controller) {
+ public void registerVibratorController(@NonNull IVibratorController controller) {
+ Objects.requireNonNull(controller);
+
synchronized (mLock) {
mVibratorControllerHolder.setVibratorController(controller);
}
@@ -134,6 +136,7 @@ final class VibratorControlService extends IVibratorControlService.Stub {
public void setVibrationParams(@SuppressLint("ArrayReturn") VibrationParam[] params,
@NonNull IVibratorController token) {
Objects.requireNonNull(token);
+ requireContainsNoNullElement(params);
synchronized (mLock) {
if (mVibratorControllerHolder.getVibratorController() == null) {
@@ -148,6 +151,13 @@ final class VibratorControlService extends IVibratorControlService.Stub {
+ "controller doesn't match the registered one. " + this);
return;
}
+ if (params == null) {
+ // Adaptive haptics scales cannot be set to null. Ignoring request.
+ Slog.d(TAG,
+ "New vibration params received but are null. New vibration "
+ + "params ignored.");
+ return;
+ }
updateAdaptiveHapticsScales(params);
recordUpdateVibrationParams(params, /* fromRequest= */ false);
@@ -181,6 +191,7 @@ final class VibratorControlService extends IVibratorControlService.Stub {
public void onRequestVibrationParamsComplete(
@NonNull IBinder requestToken, @SuppressLint("ArrayReturn") VibrationParam[] result) {
Objects.requireNonNull(requestToken);
+ requireContainsNoNullElement(result);
synchronized (mLock) {
if (mVibrationParamRequest == null) {
@@ -202,6 +213,13 @@ final class VibratorControlService extends IVibratorControlService.Stub {
long latencyMs = SystemClock.uptimeMillis() - mVibrationParamRequest.uptimeMs;
mStatsLogger.logVibrationParamRequestLatency(mVibrationParamRequest.uid, latencyMs);
+ if (result == null) {
+ Slog.d(TAG,
+ "New vibration params received but are null. New vibration "
+ + "params ignored.");
+ return;
+ }
+
updateAdaptiveHapticsScales(result);
endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ false);
recordUpdateVibrationParams(result, /* fromRequest= */ true);
@@ -401,10 +419,9 @@ final class VibratorControlService extends IVibratorControlService.Stub {
*
* @param params the new vibration params.
*/
- private void updateAdaptiveHapticsScales(@Nullable VibrationParam[] params) {
- if (params == null) {
- return;
- }
+ private void updateAdaptiveHapticsScales(@NonNull VibrationParam[] params) {
+ Objects.requireNonNull(params);
+
for (VibrationParam param : params) {
if (param.getTag() != VibrationParam.scale) {
Slog.e(TAG, "Unsupported vibration param: " + param);
@@ -448,11 +465,10 @@ final class VibratorControlService extends IVibratorControlService.Stub {
mVibrationScaler.updateAdaptiveHapticsScale(usageHint, scale);
}
- private void recordUpdateVibrationParams(@Nullable VibrationParam[] params,
+ private void recordUpdateVibrationParams(@NonNull VibrationParam[] params,
boolean fromRequest) {
- if (params == null) {
- return;
- }
+ Objects.requireNonNull(params);
+
VibrationParamsRecords.Operation operation =
fromRequest ? VibrationParamsRecords.Operation.PULL
: VibrationParamsRecords.Operation.PUSH;
@@ -474,6 +490,13 @@ final class VibratorControlService extends IVibratorControlService.Stub {
VibrationParamsRecords.Operation.CLEAR, createTime, typesMask, NO_SCALE));
}
+ private void requireContainsNoNullElement(VibrationParam[] params) {
+ if (ArrayUtils.contains(params, null)) {
+ throw new IllegalArgumentException(
+ "Invalid vibration params received: null values are not permitted.");
+ }
+ }
+
/**
* Keep records of {@link VibrationParam} values received by this service from a registered
* {@link VibratorController} and provide debug information for this service.
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index bf094ed57545..54e932a80ee9 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -658,6 +658,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
*/
private CompatDisplayInsets mCompatDisplayInsets;
+ private final TaskFragment.ConfigOverrideHint mResolveConfigHint;
+
private static ConstrainDisplayApisConfig sConstrainDisplayApisConfig;
boolean pendingVoiceInteractionStart; // Waiting for activity-invoked voice session
@@ -819,6 +821,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
@Nullable
private Rect mLetterboxBoundsForFixedOrientationAndAspectRatio;
+ // Bounds populated in resolveAspectRatioRestriction when this activity is letterboxed for
+ // aspect ratio. If not null, they are used as parent container in
+ // resolveSizeCompatModeConfiguration and in a constructor of CompatDisplayInsets.
+ @Nullable
+ private Rect mLetterboxBoundsForAspectRatio;
+
// Whether the activity is eligible to be letterboxed for fixed orientation with respect to its
// requested orientation, even when it's letterbox for another reason (e.g., size compat mode)
// and therefore #isLetterboxedForFixedOrientationAndAspectRatio returns false.
@@ -2110,6 +2118,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mLetterboxUiController = new LetterboxUiController(mWmService, this);
mCameraCompatControlEnabled = mWmService.mContext.getResources()
.getBoolean(R.bool.config_isCameraCompatControlForStretchedIssuesEnabled);
+ mResolveConfigHint = new TaskFragment.ConfigOverrideHint();
+ mResolveConfigHint.mUseLegacyInsetsForStableBounds =
+ mWmService.mFlags.mInsetsDecoupledConfiguration
+ && !info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED);
mTargetSdk = info.applicationInfo.targetSdkVersion;
@@ -8436,10 +8448,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
fullConfig.windowConfiguration.getRotation());
}
+ final Rect letterboxedContainerBounds =
+ mLetterboxBoundsForFixedOrientationAndAspectRatio != null
+ ? mLetterboxBoundsForFixedOrientationAndAspectRatio
+ : mLetterboxBoundsForAspectRatio;
// The role of CompatDisplayInsets is like the override bounds.
mCompatDisplayInsets =
new CompatDisplayInsets(
- mDisplayContent, this, mLetterboxBoundsForFixedOrientationAndAspectRatio);
+ mDisplayContent, this, letterboxedContainerBounds,
+ mResolveConfigHint.mUseLegacyInsetsForStableBounds);
}
private void clearSizeCompatModeAttributes() {
@@ -8511,6 +8528,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mIsAspectRatioApplied = false;
mIsEligibleForFixedOrientationLetterbox = false;
mLetterboxBoundsForFixedOrientationAndAspectRatio = null;
+ mLetterboxBoundsForAspectRatio = null;
// Can't use resolvedConfig.windowConfiguration.getWindowingMode() because it can be
// different from windowing mode of the task (PiP) during transition from fullscreen to PiP
@@ -8546,13 +8564,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// If the activity has requested override bounds, the configuration needs to be
// computed accordingly.
if (!matchParentBounds()) {
- getTaskFragment().computeConfigResourceOverrides(resolvedConfig,
- newParentConfiguration);
+ computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
}
// If activity in fullscreen mode is letterboxed because of fixed orientation then bounds
- // are already calculated in resolveFixedOrientationConfiguration.
+ // are already calculated in resolveFixedOrientationConfiguration, or if in size compat
+ // mode, it should already be calculated in resolveSizeCompatModeConfiguration.
// Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer.
- } else if (!isLetterboxedForFixedOrientationAndAspectRatio()
+ }
+ if (!isLetterboxedForFixedOrientationAndAspectRatio() && !mInSizeCompatModeForBounds
&& !mLetterboxUiController.hasFullscreenOverride()) {
resolveAspectRatioRestriction(newParentConfiguration);
}
@@ -8636,16 +8655,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (mDisplayContent == null) {
return;
}
- final Rect fullBounds = newParentConfiguration.windowConfiguration.getAppBounds();
+ final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds();
int rotation = newParentConfiguration.windowConfiguration.getRotation();
if (rotation == ROTATION_UNDEFINED && !isFixedRotationTransforming()) {
rotation = mDisplayContent.getRotation();
}
- if (!mWmService.mFlags.mInsetsDecoupledConfiguration
- || info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED)
+ if (!mResolveConfigHint.mUseLegacyInsetsForStableBounds
|| getCompatDisplayInsets() != null
- || isFloating(parentWindowingMode) || fullBounds == null
- || fullBounds.isEmpty() || rotation == ROTATION_UNDEFINED) {
+ || isFloating(parentWindowingMode) || parentAppBounds == null
+ || parentAppBounds.isEmpty() || rotation == ROTATION_UNDEFINED) {
// If the insets configuration decoupled logic is not enabled for the app, or the app
// already has a compat override, or the context doesn't contain enough info to
// calculate the override, skip the override.
@@ -8653,15 +8671,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
// Override starts here.
- final Rect stableInsets = mDisplayContent.getDisplayPolicy().getDecorInsetsInfo(
- rotation, fullBounds.width(), fullBounds.height()).mOverrideConfigInsets;
+ final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
+ final int dw = rotated ? mDisplayContent.mBaseDisplayHeight
+ : mDisplayContent.mBaseDisplayWidth;
+ final int dh = rotated ? mDisplayContent.mBaseDisplayWidth
+ : mDisplayContent.mBaseDisplayHeight;
+ final Rect nonDecorInsets = mDisplayContent.getDisplayPolicy()
+ .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets;
// This should be the only place override the configuration for ActivityRecord. Override
// the value if not calculated yet.
Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
if (outAppBounds == null || outAppBounds.isEmpty()) {
- inOutConfig.windowConfiguration.setAppBounds(fullBounds);
+ inOutConfig.windowConfiguration.setAppBounds(parentAppBounds);
outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
- outAppBounds.inset(stableInsets);
+ outAppBounds.inset(nonDecorInsets);
}
float density = inOutConfig.densityDpi;
if (density == Configuration.DENSITY_DPI_UNDEFINED) {
@@ -8685,11 +8708,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// For the case of PIP transition and multi-window environment, the
// smallestScreenWidthDp is handled already. Override only if the app is in
// fullscreen.
- final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
DisplayInfo info = new DisplayInfo();
mDisplayContent.getDisplay().getDisplayInfo(info);
- mDisplayContent.computeSizeRanges(info, rotated, info.logicalWidth,
- info.logicalHeight, mDisplayContent.getDisplayMetrics().density,
+ mDisplayContent.computeSizeRanges(info, rotated, dw, dh,
+ mDisplayContent.getDisplayMetrics().density,
inOutConfig, true /* overrideConfig */);
}
@@ -8702,14 +8724,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
}
- /**
- * @return The orientation to use to understand if reachability is enabled.
- */
- @Configuration.Orientation
- int getOrientationForReachability() {
- return mLetterboxUiController.hasInheritedLetterboxBehavior()
- ? mLetterboxUiController.getInheritedOrientation()
- : getRequestedConfigurationOrientation();
+ private void computeConfigByResolveHint(@NonNull Configuration resolvedConfig,
+ @NonNull Configuration parentConfig) {
+ task.computeConfigResourceOverrides(resolvedConfig, parentConfig, mResolveConfigHint);
+ // Reset the temp info which should only take effect for the specified computation.
+ mResolveConfigHint.mTmpCompatInsets = null;
+ mResolveConfigHint.mTmpOverrideDisplayInfo = null;
}
/**
@@ -8852,7 +8872,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
// Since bounds has changed, the configuration needs to be computed accordingly.
- getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration);
+ computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
// The position of configuration bounds were calculated in screen space because that is
// easier to resolve the relative position in parent container. However, if the activity is
@@ -8946,17 +8966,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* to compute the stable bounds.
* @param outStableBounds will store the stable bounds, which are the bounds with insets
* applied, if orientation is not respected when insets are applied.
- * Otherwise outStableBounds will be empty. Stable bounds should be used
- * to compute letterboxed bounds if orientation is not respected when
- * insets are applied.
+ * Stable bounds should be used to compute letterboxed bounds if
+ * orientation is not respected when insets are applied.
+ * @param outNonDecorBounds will store the non decor bounds, which are the bounds with non
+ * decor insets applied, like display cutout and nav bar.
*/
- private boolean orientationRespectedWithInsets(Rect parentBounds, Rect outStableBounds) {
+ private boolean orientationRespectedWithInsets(Rect parentBounds, Rect outStableBounds,
+ Rect outNonDecorBounds) {
outStableBounds.setEmpty();
if (mDisplayContent == null) {
return true;
}
- if (mWmService.mFlags.mInsetsDecoupledConfiguration
- && info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED)) {
+ if (!mResolveConfigHint.mUseLegacyInsetsForStableBounds) {
// No insets should be considered any more.
return true;
}
@@ -8973,8 +8994,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
? getFixedRotationTransformDisplayInfo()
: mDisplayContent.getDisplayInfo();
final Task task = getTask();
- task.calculateInsetFrames(mTmpBounds /* outNonDecorBounds */,
- outStableBounds /* outStableBounds */, parentBounds /* bounds */, di);
+ task.calculateInsetFrames(outNonDecorBounds /* outNonDecorBounds */,
+ outStableBounds /* outStableBounds */, parentBounds /* bounds */, di,
+ mResolveConfigHint.mUseLegacyInsetsForStableBounds);
final int orientationWithInsets = outStableBounds.height() >= outStableBounds.width()
? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
// If orientation does not match the orientation with insets applied, then a
@@ -8984,9 +9006,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// have the desired orientation.
final boolean orientationRespectedWithInsets = orientation == orientationWithInsets
|| orientationWithInsets == requestedOrientation;
- if (orientationRespectedWithInsets) {
- outStableBounds.setEmpty();
- }
return orientationRespectedWithInsets;
}
@@ -9012,9 +9031,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig) {
final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
final Rect stableBounds = new Rect();
+ final Rect outNonDecorBounds = mTmpBounds;
// If orientation is respected when insets are applied, then stableBounds will be empty.
boolean orientationRespectedWithInsets =
- orientationRespectedWithInsets(parentBounds, stableBounds);
+ orientationRespectedWithInsets(parentBounds, stableBounds, outNonDecorBounds);
if (orientationRespectedWithInsets && handlesOrientationChangeFromDescendant(
getOverrideOrientation())) {
// No need to letterbox because of fixed orientation. Display will handle
@@ -9031,7 +9051,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
final Rect resolvedBounds =
getResolvedOverrideConfiguration().windowConfiguration.getBounds();
- final int parentOrientation = newParentConfig.orientation;
+ final int stableBoundsOrientation = stableBounds.width() > stableBounds.height()
+ ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
+ final int parentOrientation = mResolveConfigHint.mUseLegacyInsetsForStableBounds
+ ? stableBoundsOrientation : newParentConfig.orientation;
// If the activity requires a different orientation (either by override or activityInfo),
// make it fit the available bounds by scaling down its bounds.
@@ -9046,7 +9069,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
final CompatDisplayInsets compatDisplayInsets = getCompatDisplayInsets();
- if (compatDisplayInsets != null && !compatDisplayInsets.mIsInFixedOrientationLetterbox) {
+ if (compatDisplayInsets != null
+ && !compatDisplayInsets.mIsInFixedOrientationOrAspectRatioLetterbox) {
// App prefers to keep its original size.
// If the size compat is from previous fixed orientation letterboxing, we may want to
// have fixed orientation letterbox again, otherwise it will show the size compat
@@ -9054,10 +9078,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return;
}
+ final Rect parentAppBounds = mResolveConfigHint.mUseLegacyInsetsForStableBounds
+ ? outNonDecorBounds : newParentConfig.windowConfiguration.getAppBounds();
// TODO(b/182268157): Explore using only one type of parentBoundsWithInsets, either app
// bounds or stable bounds to unify aspect ratio logic.
final Rect parentBoundsWithInsets = orientationRespectedWithInsets
- ? newParentConfig.windowConfiguration.getAppBounds() : stableBounds;
+ ? parentAppBounds : stableBounds;
final Rect containingBounds = new Rect();
final Rect containingBoundsWithInsets = new Rect();
// Need to shrink the containing bounds into a square because the parent orientation
@@ -9134,8 +9160,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// Calculate app bounds using fixed orientation bounds because they will be needed later
// for comparison with size compat app bounds in {@link resolveSizeCompatModeConfiguration}.
- getTaskFragment().computeConfigResourceOverrides(getResolvedOverrideConfiguration(),
- newParentConfig, compatDisplayInsets);
+ mResolveConfigHint.mTmpCompatInsets = compatDisplayInsets;
+ computeConfigByResolveHint(getResolvedOverrideConfiguration(), newParentConfig);
mLetterboxBoundsForFixedOrientationAndAspectRatio = new Rect(resolvedBounds);
}
@@ -9176,8 +9202,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (!resolvedBounds.isEmpty() && !resolvedBounds.equals(parentBounds)) {
// Compute the configuration based on the resolved bounds. If aspect ratio doesn't
// restrict, the bounds should be the requested override bounds.
- getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
- getFixedRotationTransformDisplayInfo());
+ mResolveConfigHint.mTmpOverrideDisplayInfo = getFixedRotationTransformDisplayInfo();
+ computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
+ mLetterboxBoundsForAspectRatio = new Rect(resolvedBounds);
}
}
@@ -9241,8 +9268,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// Use resolvedBounds to compute other override configurations such as appBounds. The bounds
// are calculated in compat container space. The actual position on screen will be applied
// later, so the calculation is simpler that doesn't need to involve offset from parent.
- getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
- compatDisplayInsets);
+ mResolveConfigHint.mTmpCompatInsets = compatDisplayInsets;
+ computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
// Use current screen layout as source because the size of app is independent to parent.
resolvedConfig.screenLayout = computeScreenLayout(
getConfiguration().screenLayout, resolvedConfig.screenWidthDp,
@@ -10741,10 +10768,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
/** Whether the {@link Task} windowingMode represents a floating window*/
final boolean mIsFloating;
/**
- * Whether is letterboxed because of fixed orientation when the unresizable activity is
- * first shown.
+ * Whether is letterboxed because of fixed orientation or aspect ratio when
+ * the unresizable activity is first shown.
*/
- final boolean mIsInFixedOrientationLetterbox;
+ final boolean mIsInFixedOrientationOrAspectRatioLetterbox;
/**
* The nonDecorInsets for each rotation. Includes the navigation bar and cutout insets. It
* is used to compute the appBounds.
@@ -10759,7 +10786,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
/** Constructs the environment to simulate the bounds behavior of the given container. */
CompatDisplayInsets(DisplayContent display, ActivityRecord container,
- @Nullable Rect fixedOrientationBounds) {
+ @Nullable Rect letterboxedContainerBounds, boolean useOverrideInsets) {
mOriginalRotation = display.getRotation();
mIsFloating = container.getWindowConfiguration().tasksAreFloating();
mOriginalRequestedOrientation = container.getRequestedConfigurationOrientation();
@@ -10774,22 +10801,22 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mNonDecorInsets[rotation] = emptyRect;
mStableInsets[rotation] = emptyRect;
}
- mIsInFixedOrientationLetterbox = false;
+ mIsInFixedOrientationOrAspectRatioLetterbox = false;
return;
}
final Task task = container.getTask();
- mIsInFixedOrientationLetterbox = fixedOrientationBounds != null;
+ mIsInFixedOrientationOrAspectRatioLetterbox = letterboxedContainerBounds != null;
// Store the bounds of the Task for the non-resizable activity to use in size compat
// mode so that the activity will not be resized regardless the windowing mode it is
// currently in.
- // When an activity needs to be letterboxed because of fixed orientation, use fixed
- // orientation bounds instead of task bounds since the activity will be displayed
- // within these even if it is in size compat mode.
- final Rect filledContainerBounds = mIsInFixedOrientationLetterbox
- ? fixedOrientationBounds
+ // When an activity needs to be letterboxed because of fixed orientation or aspect
+ // ratio, use resolved bounds instead of task bounds since the activity will be
+ // displayed within these even if it is in size compat mode.
+ final Rect filledContainerBounds = mIsInFixedOrientationOrAspectRatioLetterbox
+ ? letterboxedContainerBounds
: task != null ? task.getBounds() : display.getBounds();
final int filledContainerRotation = task != null
? task.getConfiguration().windowConfiguration.getRotation()
@@ -10811,8 +10838,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
final int dh = rotated ? display.mBaseDisplayWidth : display.mBaseDisplayHeight;
final DisplayPolicy.DecorInsets.Info decorInfo =
policy.getDecorInsetsInfo(rotation, dw, dh);
- mNonDecorInsets[rotation].set(decorInfo.mNonDecorInsets);
- mStableInsets[rotation].set(decorInfo.mConfigInsets);
+ if (useOverrideInsets) {
+ mStableInsets[rotation].set(decorInfo.mOverrideConfigInsets);
+ mNonDecorInsets[rotation].set(decorInfo.mOverrideNonDecorInsets);
+ } else {
+ mStableInsets[rotation].set(decorInfo.mConfigInsets);
+ mNonDecorInsets[rotation].set(decorInfo.mNonDecorInsets);
+ }
if (unfilledContainerBounds == null) {
continue;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 2c39c5875389..08baf3b60eda 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2524,7 +2524,9 @@ class ActivityStarter {
// If the caller has asked not to resume at this point, we make note
// of this in the record so that we can skip it when trying to find
// the top running activity.
- if (!r.showToCurrentUser() || mLaunchTaskBehind) {
+ final boolean canShowActivity = r.showToCurrentUser();
+ if (!canShowActivity) Slog.w(TAG, "Can't resume non-current user r=" + r);
+ if (!canShowActivity || mLaunchTaskBehind) {
r.delayedResume = true;
mDoResume = false;
} else {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index d984fb14f523..a739e577bca3 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -886,6 +886,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
mRecentTasks.onSystemReadyLocked();
mTaskSupervisor.onSystemReady();
mActivityClientController.onSystemReady();
+ mAppWarnings.onSystemReady();
// TODO(b/258792202) Cleanup once ASM is ready to launch
ActivitySecurityModelFeatureFlags.initialize(mContext.getMainExecutor());
mGrammaticalManagerInternal = LocalServices.getService(
@@ -3782,25 +3783,18 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
EventLogTags.writeWmEnterPip(r.mUserId, System.identityHashCode(r),
r.shortComponentName, Boolean.toString(isAutoEnter));
-
- // 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();
- }
+ 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;
}
};
@@ -6360,7 +6354,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
public void onPackageDataCleared(String name, int userId) {
synchronized (mGlobalLock) {
mCompatModePackages.handlePackageDataClearedLocked(name);
- mAppWarnings.onPackageDataCleared(name);
+ mAppWarnings.onPackageDataCleared(name, userId);
mPackageConfigPersister.onPackageDataCleared(name, userId);
}
}
@@ -6368,7 +6362,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@Override
public void onPackageUninstalled(String name, int userId) {
synchronized (mGlobalLock) {
- mAppWarnings.onPackageUninstalled(name);
+ mAppWarnings.onPackageUninstalled(name, userId);
mCompatModePackages.handlePackageUninstalledLocked(name);
mPackageConfigPersister.onPackageUninstall(name, userId);
}
diff --git a/services/core/java/com/android/server/wm/AppWarnings.java b/services/core/java/com/android/server/wm/AppWarnings.java
index ad5f4427fbc4..9fd543fca30e 100644
--- a/services/core/java/com/android/server/wm/AppWarnings.java
+++ b/services/core/java/com/android/server/wm/AppWarnings.java
@@ -16,8 +16,14 @@
package com.android.server.wm;
+import static android.os.UserHandle.USER_NULL;
+import static android.os.UserHandle.USER_SYSTEM;
+import static android.os.UserManager.isHeadlessSystemUserMode;
+import static android.os.UserManager.isVisibleBackgroundUsersEnabled;
+
import android.annotation.NonNull;
import android.annotation.UiThread;
+import android.annotation.UserIdInt;
import android.annotation.WorkerThread;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
@@ -26,17 +32,21 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
+import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemProperties;
+import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.DisplayMetrics;
+import android.util.Pair;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
@@ -44,6 +54,8 @@ import com.android.internal.util.ArrayUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.IoThread;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -65,19 +77,30 @@ class AppWarnings {
public static final int FLAG_HIDE_DEPRECATED_SDK = 0x04;
public static final int FLAG_HIDE_DEPRECATED_ABI = 0x08;
+ /**
+ * Map of package flags for each user.
+ * Key: {@literal Pair<userId, packageName>}
+ * Value: Flags
+ */
@GuardedBy("mPackageFlags")
- private final ArrayMap<String, Integer> mPackageFlags = new ArrayMap<>();
+ private final ArrayMap<Pair<Integer, String>, Integer> mPackageFlags = new ArrayMap<>();
private final ActivityTaskManagerService mAtm;
- private final Context mUiContext;
private final WriteConfigTask mWriteConfigTask;
private final UiHandler mUiHandler;
private final AtomicFile mConfigFile;
- private UnsupportedDisplaySizeDialog mUnsupportedDisplaySizeDialog;
- private UnsupportedCompileSdkDialog mUnsupportedCompileSdkDialog;
- private DeprecatedTargetSdkVersionDialog mDeprecatedTargetSdkVersionDialog;
- private DeprecatedAbiDialog mDeprecatedAbiDialog;
+ private UserManagerInternal mUserManagerInternal;
+
+ /**
+ * Maps of app warning dialogs for each user.
+ * Key: userId
+ * Value: The warning dialog for specific user
+ */
+ private SparseArray<UnsupportedDisplaySizeDialog> mUnsupportedDisplaySizeDialogs;
+ private SparseArray<UnsupportedCompileSdkDialog> mUnsupportedCompileSdkDialogs;
+ private SparseArray<DeprecatedTargetSdkVersionDialog> mDeprecatedTargetSdkVersionDialogs;
+ private SparseArray<DeprecatedAbiDialog> mDeprecatedAbiDialogs;
/** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */
private final ArraySet<ComponentName> mAlwaysShowUnsupportedCompileSdkWarningActivities =
@@ -92,12 +115,35 @@ class AppWarnings {
public AppWarnings(ActivityTaskManagerService atm, Context uiContext, Handler handler,
Handler uiHandler, File systemDir) {
mAtm = atm;
- mUiContext = uiContext;
mWriteConfigTask = new WriteConfigTask();
mUiHandler = new UiHandler(uiHandler.getLooper());
mConfigFile = new AtomicFile(new File(systemDir, CONFIG_FILE_NAME), "warnings-config");
+ }
+ /**
+ * Called when ActivityManagerService receives its systemReady call during boot.
+ */
+ void onSystemReady() {
+ mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
readConfigFromFileAmsThread();
+
+ if (!isVisibleBackgroundUsersEnabled()) {
+ return;
+ }
+
+ mUserManagerInternal.addUserLifecycleListener(
+ new UserManagerInternal.UserLifecycleListener() {
+ @Override
+ public void onUserRemoved(UserInfo user) {
+ // Ignore profile user.
+ if (!user.isFull()) {
+ return;
+ }
+ // Dismiss all warnings and clear all package flags for the user.
+ mUiHandler.hideDialogsForPackage(/* name= */ null, user.id);
+ clearAllPackageFlagsForUser(user.id);
+ }
+ });
}
/**
@@ -227,18 +273,20 @@ class AppWarnings {
* Called by ActivityManagerService when package data has been cleared.
*
* @param name the package whose data has been cleared
+ * @param userId the user where the package resides.
*/
- public void onPackageDataCleared(String name) {
- removePackageAndHideDialogs(name);
+ public void onPackageDataCleared(String name, int userId) {
+ removePackageAndHideDialogs(name, userId);
}
/**
* Called by ActivityManagerService when a package has been uninstalled.
*
* @param name the package that has been uninstalled
+ * @param userId the user where the package resides.
*/
- public void onPackageUninstalled(String name) {
- removePackageAndHideDialogs(name);
+ public void onPackageUninstalled(String name, int userId) {
+ removePackageAndHideDialogs(name, userId);
}
/**
@@ -251,11 +299,24 @@ class AppWarnings {
/**
* Does what it says on the tin.
*/
- private void removePackageAndHideDialogs(String name) {
- mUiHandler.hideDialogsForPackage(name);
+ private void removePackageAndHideDialogs(String name, int userId) {
+ // Per-user AppWarnings only affects the behavior of the devices that enable the visible
+ // background users.
+ // To preserve existing behavior of the other devices, handle AppWarnings as a system user
+ // regardless of the actual user.
+ if (!isVisibleBackgroundUsersEnabled()) {
+ userId = USER_SYSTEM;
+ } else {
+ // If the userId is of a profile, use the parent user ID,
+ // since the warning dialogs and the flags for a package are handled per profile group.
+ userId = mUserManagerInternal.getProfileParentId(userId);
+ }
+
+ mUiHandler.hideDialogsForPackage(name, userId);
synchronized (mPackageFlags) {
- if (mPackageFlags.remove(name) != null) {
+ final Pair<Integer, String> packageKey = Pair.create(userId, name);
+ if (mPackageFlags.remove(packageKey) != null) {
mWriteConfigTask.schedule();
}
}
@@ -268,10 +329,14 @@ class AppWarnings {
*/
@UiThread
private void hideUnsupportedDisplaySizeDialogUiThread() {
- if (mUnsupportedDisplaySizeDialog != null) {
- mUnsupportedDisplaySizeDialog.dismiss();
- mUnsupportedDisplaySizeDialog = null;
+ if (mUnsupportedDisplaySizeDialogs == null) {
+ return;
}
+
+ for (int i = 0; i < mUnsupportedDisplaySizeDialogs.size(); i++) {
+ mUnsupportedDisplaySizeDialogs.valueAt(i).dismiss();
+ }
+ mUnsupportedDisplaySizeDialogs.clear();
}
/**
@@ -282,16 +347,24 @@ class AppWarnings {
* @param ar record for the activity that triggered the warning
*/
@UiThread
- private void showUnsupportedDisplaySizeDialogUiThread(ActivityRecord ar) {
- if (mUnsupportedDisplaySizeDialog != null) {
- mUnsupportedDisplaySizeDialog.dismiss();
- mUnsupportedDisplaySizeDialog = null;
+ private void showUnsupportedDisplaySizeDialogUiThread(@NonNull ActivityRecord ar) {
+ final int userId = getUserIdForActivity(ar);
+ UnsupportedDisplaySizeDialog unsupportedDisplaySizeDialog;
+ if (mUnsupportedDisplaySizeDialogs != null) {
+ unsupportedDisplaySizeDialog = mUnsupportedDisplaySizeDialogs.get(userId);
+ if (unsupportedDisplaySizeDialog != null) {
+ unsupportedDisplaySizeDialog.dismiss();
+ mUnsupportedDisplaySizeDialogs.remove(userId);
+ }
}
- if (ar != null && !hasPackageFlag(
- ar.packageName, FLAG_HIDE_DISPLAY_SIZE)) {
- mUnsupportedDisplaySizeDialog = new UnsupportedDisplaySizeDialog(
- AppWarnings.this, mUiContext, ar.info.applicationInfo);
- mUnsupportedDisplaySizeDialog.show();
+ if (!hasPackageFlag(userId, ar.packageName, FLAG_HIDE_DISPLAY_SIZE)) {
+ unsupportedDisplaySizeDialog = new UnsupportedDisplaySizeDialog(
+ AppWarnings.this, getUiContextForActivity(ar), ar.info.applicationInfo, userId);
+ unsupportedDisplaySizeDialog.show();
+ if (mUnsupportedDisplaySizeDialogs == null) {
+ mUnsupportedDisplaySizeDialogs = new SparseArray<>();
+ }
+ mUnsupportedDisplaySizeDialogs.put(userId, unsupportedDisplaySizeDialog);
}
}
@@ -303,16 +376,24 @@ class AppWarnings {
* @param ar record for the activity that triggered the warning
*/
@UiThread
- private void showUnsupportedCompileSdkDialogUiThread(ActivityRecord ar) {
- if (mUnsupportedCompileSdkDialog != null) {
- mUnsupportedCompileSdkDialog.dismiss();
- mUnsupportedCompileSdkDialog = null;
+ private void showUnsupportedCompileSdkDialogUiThread(@NonNull ActivityRecord ar) {
+ final int userId = getUserIdForActivity(ar);
+ UnsupportedCompileSdkDialog unsupportedCompileSdkDialog;
+ if (mUnsupportedCompileSdkDialogs != null) {
+ unsupportedCompileSdkDialog = mUnsupportedCompileSdkDialogs.get(userId);
+ if (unsupportedCompileSdkDialog != null) {
+ unsupportedCompileSdkDialog.dismiss();
+ mUnsupportedCompileSdkDialogs.remove(userId);
+ }
}
- if (ar != null && !hasPackageFlag(
- ar.packageName, FLAG_HIDE_COMPILE_SDK)) {
- mUnsupportedCompileSdkDialog = new UnsupportedCompileSdkDialog(
- AppWarnings.this, mUiContext, ar.info.applicationInfo);
- mUnsupportedCompileSdkDialog.show();
+ if (!hasPackageFlag(userId, ar.packageName, FLAG_HIDE_COMPILE_SDK)) {
+ unsupportedCompileSdkDialog = new UnsupportedCompileSdkDialog(
+ AppWarnings.this, getUiContextForActivity(ar), ar.info.applicationInfo, userId);
+ unsupportedCompileSdkDialog.show();
+ if (mUnsupportedCompileSdkDialogs == null) {
+ mUnsupportedCompileSdkDialogs = new SparseArray<>();
+ }
+ mUnsupportedCompileSdkDialogs.put(userId, unsupportedCompileSdkDialog);
}
}
@@ -324,16 +405,24 @@ class AppWarnings {
* @param ar record for the activity that triggered the warning
*/
@UiThread
- private void showDeprecatedTargetSdkDialogUiThread(ActivityRecord ar) {
- if (mDeprecatedTargetSdkVersionDialog != null) {
- mDeprecatedTargetSdkVersionDialog.dismiss();
- mDeprecatedTargetSdkVersionDialog = null;
+ private void showDeprecatedTargetSdkDialogUiThread(@NonNull ActivityRecord ar) {
+ final int userId = getUserIdForActivity(ar);
+ DeprecatedTargetSdkVersionDialog deprecatedTargetSdkVersionDialog;
+ if (mDeprecatedTargetSdkVersionDialogs != null) {
+ deprecatedTargetSdkVersionDialog = mDeprecatedTargetSdkVersionDialogs.get(userId);
+ if (deprecatedTargetSdkVersionDialog != null) {
+ deprecatedTargetSdkVersionDialog.dismiss();
+ mDeprecatedTargetSdkVersionDialogs.remove(userId);
+ }
}
- if (ar != null && !hasPackageFlag(
- ar.packageName, FLAG_HIDE_DEPRECATED_SDK)) {
- mDeprecatedTargetSdkVersionDialog = new DeprecatedTargetSdkVersionDialog(
- AppWarnings.this, mUiContext, ar.info.applicationInfo);
- mDeprecatedTargetSdkVersionDialog.show();
+ if (!hasPackageFlag(userId, ar.packageName, FLAG_HIDE_DEPRECATED_SDK)) {
+ deprecatedTargetSdkVersionDialog = new DeprecatedTargetSdkVersionDialog(
+ AppWarnings.this, getUiContextForActivity(ar), ar.info.applicationInfo, userId);
+ deprecatedTargetSdkVersionDialog.show();
+ if (mDeprecatedTargetSdkVersionDialogs == null) {
+ mDeprecatedTargetSdkVersionDialogs = new SparseArray<>();
+ }
+ mDeprecatedTargetSdkVersionDialogs.put(userId, deprecatedTargetSdkVersionDialog);
}
}
@@ -345,16 +434,24 @@ class AppWarnings {
* @param ar record for the activity that triggered the warning
*/
@UiThread
- private void showDeprecatedAbiDialogUiThread(ActivityRecord ar) {
- if (mDeprecatedAbiDialog != null) {
- mDeprecatedAbiDialog.dismiss();
- mDeprecatedAbiDialog = null;
+ private void showDeprecatedAbiDialogUiThread(@NonNull ActivityRecord ar) {
+ final int userId = getUserIdForActivity(ar);
+ DeprecatedAbiDialog deprecatedAbiDialog;
+ if (mDeprecatedAbiDialogs != null) {
+ deprecatedAbiDialog = mDeprecatedAbiDialogs.get(userId);
+ if (deprecatedAbiDialog != null) {
+ deprecatedAbiDialog.dismiss();
+ mDeprecatedAbiDialogs.remove(userId);
+ }
}
- if (ar != null && !hasPackageFlag(
- ar.packageName, FLAG_HIDE_DEPRECATED_ABI)) {
- mDeprecatedAbiDialog = new DeprecatedAbiDialog(
- AppWarnings.this, mUiContext, ar.info.applicationInfo);
- mDeprecatedAbiDialog.show();
+ if (!hasPackageFlag(userId, ar.packageName, FLAG_HIDE_DEPRECATED_ABI)) {
+ deprecatedAbiDialog = new DeprecatedAbiDialog(
+ AppWarnings.this, getUiContextForActivity(ar), ar.info.applicationInfo, userId);
+ deprecatedAbiDialog.show();
+ if (mDeprecatedAbiDialogs == null) {
+ mDeprecatedAbiDialogs = new SparseArray<>();
+ }
+ mDeprecatedAbiDialogs.put(userId, deprecatedAbiDialog);
}
}
@@ -365,65 +462,84 @@ class AppWarnings {
*
* @param name the package for which warnings should be dismissed, or {@code null} to dismiss
* all warnings
+ * @param userId the user where the package resides.
*/
@UiThread
- private void hideDialogsForPackageUiThread(String name) {
+ private void hideDialogsForPackageUiThread(String name, int userId) {
// Hides the "unsupported display" dialog if necessary.
- if (mUnsupportedDisplaySizeDialog != null && (name == null || name.equals(
- mUnsupportedDisplaySizeDialog.mPackageName))) {
- mUnsupportedDisplaySizeDialog.dismiss();
- mUnsupportedDisplaySizeDialog = null;
+ if (mUnsupportedDisplaySizeDialogs != null) {
+ UnsupportedDisplaySizeDialog unsupportedDisplaySizeDialog =
+ mUnsupportedDisplaySizeDialogs.get(userId);
+ if (unsupportedDisplaySizeDialog != null && (name == null || name.equals(
+ unsupportedDisplaySizeDialog.mPackageName))) {
+ unsupportedDisplaySizeDialog.dismiss();
+ mUnsupportedDisplaySizeDialogs.remove(userId);
+ }
}
// Hides the "unsupported compile SDK" dialog if necessary.
- if (mUnsupportedCompileSdkDialog != null && (name == null || name.equals(
- mUnsupportedCompileSdkDialog.mPackageName))) {
- mUnsupportedCompileSdkDialog.dismiss();
- mUnsupportedCompileSdkDialog = null;
+ if (mUnsupportedCompileSdkDialogs != null) {
+ UnsupportedCompileSdkDialog unsupportedCompileSdkDialog =
+ mUnsupportedCompileSdkDialogs.get(userId);
+ if (unsupportedCompileSdkDialog != null && (name == null || name.equals(
+ unsupportedCompileSdkDialog.mPackageName))) {
+ unsupportedCompileSdkDialog.dismiss();
+ mUnsupportedCompileSdkDialogs.remove(userId);
+ }
}
// Hides the "deprecated target sdk version" dialog if necessary.
- if (mDeprecatedTargetSdkVersionDialog != null && (name == null || name.equals(
- mDeprecatedTargetSdkVersionDialog.mPackageName))) {
- mDeprecatedTargetSdkVersionDialog.dismiss();
- mDeprecatedTargetSdkVersionDialog = null;
+ if (mDeprecatedTargetSdkVersionDialogs != null) {
+ DeprecatedTargetSdkVersionDialog deprecatedTargetSdkVersionDialog =
+ mDeprecatedTargetSdkVersionDialogs.get(userId);
+ if (deprecatedTargetSdkVersionDialog != null && (name == null || name.equals(
+ deprecatedTargetSdkVersionDialog.mPackageName))) {
+ deprecatedTargetSdkVersionDialog.dismiss();
+ mDeprecatedTargetSdkVersionDialogs.remove(userId);
+ }
}
// Hides the "deprecated abi" dialog if necessary.
- if (mDeprecatedAbiDialog != null && (name == null || name.equals(
- mDeprecatedAbiDialog.mPackageName))) {
- mDeprecatedAbiDialog.dismiss();
- mDeprecatedAbiDialog = null;
+ if (mDeprecatedAbiDialogs != null) {
+ DeprecatedAbiDialog deprecatedAbiDialog = mDeprecatedAbiDialogs.get(userId);
+ if (deprecatedAbiDialog != null && (name == null || name.equals(
+ deprecatedAbiDialog.mPackageName))) {
+ deprecatedAbiDialog.dismiss();
+ mDeprecatedAbiDialogs.remove(userId);
+ }
}
}
/**
* Returns the value of the flag for the given package.
*
+ * @param userId the user where the package resides.
* @param name the package from which to retrieve the flag
* @param flag the bitmask for the flag to retrieve
* @return {@code true} if the flag is enabled, {@code false} otherwise
*/
- boolean hasPackageFlag(String name, int flag) {
- return (getPackageFlags(name) & flag) == flag;
+ boolean hasPackageFlag(int userId, String name, int flag) {
+ return (getPackageFlags(userId, name) & flag) == flag;
}
/**
* Sets the flag for the given package to the specified value.
*
+ * @param userId the user where the package resides.
* @param name the package on which to set the flag
* @param flag the bitmask for flag to set
* @param enabled the value to set for the flag
*/
- void setPackageFlag(String name, int flag, boolean enabled) {
+ void setPackageFlag(int userId, String name, int flag, boolean enabled) {
synchronized (mPackageFlags) {
- final int curFlags = getPackageFlags(name);
+ final int curFlags = getPackageFlags(userId, name);
final int newFlags = enabled ? (curFlags | flag) : (curFlags & ~flag);
if (curFlags != newFlags) {
+ final Pair<Integer, String> packageKey = Pair.create(userId, name);
if (newFlags != 0) {
- mPackageFlags.put(name, newFlags);
+ mPackageFlags.put(packageKey, newFlags);
} else {
- mPackageFlags.remove(name);
+ mPackageFlags.remove(packageKey);
}
mWriteConfigTask.schedule();
}
@@ -433,13 +549,95 @@ class AppWarnings {
/**
* Returns the bitmask of flags set for the specified package.
*/
- private int getPackageFlags(String name) {
+ private int getPackageFlags(int userId, String packageName) {
synchronized (mPackageFlags) {
- return mPackageFlags.getOrDefault(name, 0);
+ final Pair<Integer, String> packageKey = Pair.create(userId, packageName);
+ return mPackageFlags.getOrDefault(packageKey, 0);
}
}
/**
+ * Clear all the package flags for given user.
+ */
+ private void clearAllPackageFlagsForUser(int userId) {
+ synchronized (mPackageFlags) {
+ boolean hasPackageFlagsForUser = false;
+ for (int i = mPackageFlags.size() - 1; i >= 0; i--) {
+ Pair<Integer, String> key = mPackageFlags.keyAt(i);
+ if (key.first == userId) {
+ hasPackageFlagsForUser = true;
+ mPackageFlags.remove(key);
+ }
+ }
+
+ if (hasPackageFlagsForUser) {
+ mWriteConfigTask.schedule();
+ }
+ }
+ }
+
+ /**
+ * Returns the user ID for handling AppWarnings per user.
+ * Per-user AppWarnings only affects the behavior of the devices that enable
+ * the visible background users.
+ * If the device doesn't enable visible background users, it will return the system user ID
+ * for handling AppWarnings as a system user regardless of the actual user
+ * to preserve existing behavior of the device.
+ * Otherwise, it will return the main user (i.e., not a profile) that is assigned to the display
+ * where the activity is launched.
+ */
+ private @UserIdInt int getUserIdForActivity(@NonNull ActivityRecord ar) {
+ if (!isVisibleBackgroundUsersEnabled()) {
+ return USER_SYSTEM;
+ }
+
+ if (ar.mUserId == USER_SYSTEM) {
+ return getUserAssignedToDisplay(ar.mDisplayContent.getDisplayId());
+ }
+
+ return mUserManagerInternal.getProfileParentId(ar.mUserId);
+ }
+
+ /**
+ * Returns the UI context for handling AppWarnings per user.
+ * Per-user AppWarnings only affects the behavior of the devices that enable
+ * the visible background users.
+ * If the device enables the visible background users, it will return the UI context associated
+ * with the assigned user and the display where the activity is launched.
+ * If the HSUM device doesn't enable the visible background users, it will return the UI context
+ * associated with the current user and the default display.
+ * Otherwise, it will return the UI context associated with the system user and the default
+ * display.
+ */
+ private Context getUiContextForActivity(@NonNull ActivityRecord ar) {
+ if (!isVisibleBackgroundUsersEnabled()) {
+ if (!isHeadlessSystemUserMode()) {
+ return mAtm.getUiContext();
+ }
+
+ Context uiContextForCurrentUser = mAtm.getUiContext().createContextAsUser(
+ new UserHandle(mAtm.getCurrentUserId()), /* flags= */ 0);
+ return uiContextForCurrentUser;
+ }
+
+ DisplayContent dc = ar.mDisplayContent;
+ Context systemUiContext = dc.getDisplayPolicy().getSystemUiContext();
+ int assignedUser = getUserAssignedToDisplay(dc.getDisplayId());
+ Context uiContextForUser = systemUiContext.createContextAsUser(
+ new UserHandle(assignedUser), /* flags= */ 0);
+ return uiContextForUser;
+ }
+
+ /**
+ * Returns the main user that is assigned to the display.
+ *
+ * See {@link UserManagerInternal#getUserAssignedToDisplay(int)}.
+ */
+ private @UserIdInt int getUserAssignedToDisplay(int displayId) {
+ return mUserManagerInternal.getUserAssignedToDisplay(displayId);
+ }
+
+ /**
* Handles messages on the system process UI thread.
*/
private final class UiHandler extends Handler {
@@ -470,7 +668,8 @@ class AppWarnings {
} break;
case MSG_HIDE_DIALOGS_FOR_PACKAGE: {
final String name = (String) msg.obj;
- hideDialogsForPackageUiThread(name);
+ final int userId = (int) msg.arg1;
+ hideDialogsForPackageUiThread(name, userId);
} break;
case MSG_SHOW_DEPRECATED_TARGET_SDK_DIALOG: {
final ActivityRecord ar = (ActivityRecord) msg.obj;
@@ -508,20 +707,24 @@ class AppWarnings {
obtainMessage(MSG_SHOW_DEPRECATED_ABI_DIALOG, r).sendToTarget();
}
- public void hideDialogsForPackage(String name) {
- obtainMessage(MSG_HIDE_DIALOGS_FOR_PACKAGE, name).sendToTarget();
+ public void hideDialogsForPackage(String name, int userId) {
+ obtainMessage(MSG_HIDE_DIALOGS_FOR_PACKAGE, userId, 0, name).sendToTarget();
}
}
static class BaseDialog {
final AppWarnings mManager;
+ final Context mUiContext;
final String mPackageName;
+ final int mUserId;
AlertDialog mDialog;
private BroadcastReceiver mCloseReceiver;
- BaseDialog(AppWarnings manager, String packageName) {
+ BaseDialog(AppWarnings manager, Context uiContext, String packageName, int userId) {
mManager = manager;
+ mUiContext = uiContext;
mPackageName = packageName;
+ mUserId = userId;
}
@UiThread
@@ -532,11 +735,11 @@ class AppWarnings {
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
- mManager.mUiHandler.hideDialogsForPackage(mPackageName);
+ mManager.mUiHandler.hideDialogsForPackage(mPackageName, mUserId);
}
}
};
- mManager.mUiContext.registerReceiver(mCloseReceiver,
+ mUiContext.registerReceiver(mCloseReceiver,
new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS),
Context.RECEIVER_EXPORTED);
}
@@ -548,7 +751,7 @@ class AppWarnings {
void dismiss() {
if (mDialog == null) return;
if (mCloseReceiver != null) {
- mManager.mUiContext.unregisterReceiver(mCloseReceiver);
+ mUiContext.unregisterReceiver(mCloseReceiver);
mCloseReceiver = null;
}
mDialog.dismiss();
@@ -558,12 +761,13 @@ class AppWarnings {
private final class WriteConfigTask implements Runnable {
private static final long WRITE_CONFIG_DELAY_MS = 10000;
- final AtomicReference<ArrayMap<String, Integer>> mPendingPackageFlags =
+ final AtomicReference<ArrayMap<Pair<Integer, String>, Integer>> mPendingPackageFlags =
new AtomicReference<>();
@Override
public void run() {
- final ArrayMap<String, Integer> packageFlags = mPendingPackageFlags.getAndSet(null);
+ final ArrayMap<Pair<Integer, String>, Integer> packageFlags =
+ mPendingPackageFlags.getAndSet(null);
if (packageFlags != null) {
writeConfigToFile(packageFlags);
}
@@ -579,7 +783,7 @@ class AppWarnings {
/** Writes the configuration file. */
@WorkerThread
- private void writeConfigToFile(@NonNull ArrayMap<String, Integer> packageFlags) {
+ private void writeConfigToFile(@NonNull ArrayMap<Pair<Integer, String>, Integer> packageFlags) {
FileOutputStream fos = null;
try {
fos = mConfigFile.startWrite();
@@ -590,13 +794,16 @@ class AppWarnings {
out.startTag(null, "packages");
for (int i = 0; i < packageFlags.size(); i++) {
- final String pkg = packageFlags.keyAt(i);
+ final Pair<Integer, String> key = packageFlags.keyAt(i);
+ final int userId = key.first;
+ final String packageName = key.second;
final int mode = packageFlags.valueAt(i);
if (mode == 0) {
continue;
}
out.startTag(null, "package");
- out.attribute(null, "name", pkg);
+ out.attributeInt(null, "user", userId);
+ out.attribute(null, "name", packageName);
out.attributeInt(null, "flags", mode);
out.endTag(null, "package");
}
@@ -616,7 +823,7 @@ class AppWarnings {
/**
* Reads the configuration file and populates the package flags.
* <p>
- * <strong>Note:</strong> Must be called from the constructor (and thus on the
+ * <strong>Note:</strong> Must be called from #onSystemReady() (and thus on the
* ActivityManagerService thread) since we don't synchronize on config.
*/
private void readConfigFromFileAmsThread() {
@@ -639,21 +846,58 @@ class AppWarnings {
String tagName = parser.getName();
if ("packages".equals(tagName)) {
eventType = parser.next();
+ boolean writeConfigToFileNeeded = false;
do {
if (eventType == XmlPullParser.START_TAG) {
tagName = parser.getName();
if (parser.getDepth() == 2) {
if ("package".equals(tagName)) {
+ final int userId = parser.getAttributeInt(
+ null, "user", USER_NULL);
final String name = parser.getAttributeValue(null, "name");
if (name != null) {
int flagsInt = parser.getAttributeInt(null, "flags", 0);
- mPackageFlags.put(name, flagsInt);
+ if (userId != USER_NULL) {
+ final Pair<Integer, String> packageKey =
+ Pair.create(userId, name);
+ mPackageFlags.put(packageKey, flagsInt);
+ } else {
+ // This is for compatibility with existing configuration
+ // file written from legacy logic(pre-V) which does not have
+ // the flags per-user. (b/296334639)
+ writeConfigToFileNeeded = true;
+ if (!isVisibleBackgroundUsersEnabled()) {
+ // To preserve existing behavior of the devices that
+ // doesn't enable visible background users, populate
+ // the flags for a package as the system user.
+ final Pair<Integer, String> packageKey =
+ Pair.create(USER_SYSTEM, name);
+ mPackageFlags.put(packageKey, flagsInt);
+ } else {
+ // To manage the flags per user in the device that
+ // enable visible background users, populate the flags
+ // for all existing non-profile human user.
+ UserInfo[] users = mUserManagerInternal.getUserInfos();
+ for (UserInfo userInfo : users) {
+ if (!userInfo.isFull()) {
+ continue;
+ }
+ final Pair<Integer, String> packageKey =
+ Pair.create(userInfo.id, name);
+ mPackageFlags.put(packageKey, flagsInt);
+ }
+ }
+ }
}
}
}
}
eventType = parser.next();
} while (eventType != XmlPullParser.END_DOCUMENT);
+
+ if (writeConfigToFileNeeded) {
+ mWriteConfigTask.schedule();
+ }
}
} catch (XmlPullParserException e) {
Slog.w(TAG, "Error reading package metadata", e);
diff --git a/services/core/java/com/android/server/wm/DeprecatedAbiDialog.java b/services/core/java/com/android/server/wm/DeprecatedAbiDialog.java
index e96208d3d0b3..46b34a1f4e16 100644
--- a/services/core/java/com/android/server/wm/DeprecatedAbiDialog.java
+++ b/services/core/java/com/android/server/wm/DeprecatedAbiDialog.java
@@ -28,8 +28,8 @@ import com.android.internal.R;
class DeprecatedAbiDialog extends AppWarnings.BaseDialog {
DeprecatedAbiDialog(final AppWarnings manager, Context context,
- ApplicationInfo appInfo) {
- super(manager, appInfo.packageName);
+ ApplicationInfo appInfo, int userId) {
+ super(manager, context, appInfo.packageName, userId);
final PackageManager pm = context.getPackageManager();
final CharSequence label = appInfo.loadSafeLabel(pm,
@@ -41,7 +41,7 @@ class DeprecatedAbiDialog extends AppWarnings.BaseDialog {
final AlertDialog.Builder builder = new AlertDialog.Builder(context)
.setPositiveButton(R.string.ok, (dialog, which) ->
manager.setPackageFlag(
- mPackageName, AppWarnings.FLAG_HIDE_DEPRECATED_ABI, true))
+ mUserId, mPackageName, AppWarnings.FLAG_HIDE_DEPRECATED_ABI, true))
.setMessage(message)
.setTitle(label);
diff --git a/services/core/java/com/android/server/wm/DeprecatedTargetSdkVersionDialog.java b/services/core/java/com/android/server/wm/DeprecatedTargetSdkVersionDialog.java
index 1a7a9b258400..ce4238582b26 100644
--- a/services/core/java/com/android/server/wm/DeprecatedTargetSdkVersionDialog.java
+++ b/services/core/java/com/android/server/wm/DeprecatedTargetSdkVersionDialog.java
@@ -31,8 +31,8 @@ import com.android.server.utils.AppInstallerUtil;
class DeprecatedTargetSdkVersionDialog extends AppWarnings.BaseDialog {
DeprecatedTargetSdkVersionDialog(final AppWarnings manager, Context context,
- ApplicationInfo appInfo) {
- super(manager, appInfo.packageName);
+ ApplicationInfo appInfo, int userId) {
+ super(manager, context, appInfo.packageName, userId);
final PackageManager pm = context.getPackageManager();
final CharSequence label = appInfo.loadSafeLabel(pm,
@@ -44,7 +44,7 @@ class DeprecatedTargetSdkVersionDialog extends AppWarnings.BaseDialog {
final AlertDialog.Builder builder = new AlertDialog.Builder(context)
.setPositiveButton(R.string.ok, (dialog, which) ->
manager.setPackageFlag(
- mPackageName, AppWarnings.FLAG_HIDE_DEPRECATED_SDK, true))
+ mUserId, mPackageName, AppWarnings.FLAG_HIDE_DEPRECATED_SDK, true))
.setMessage(message)
.setTitle(label);
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 5e0d4f9a33f9..84dadab7da59 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -41,7 +41,6 @@ import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACK
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_CONSUME_IME_INSETS;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
@@ -986,19 +985,6 @@ public class DisplayPolicy {
+ " to fit insets. fitInsetsTypes=" + WindowInsets.Type.toString(
attrs.getFitInsetsTypes()));
}
- if ((attrs.privateFlags & PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED) != 0
- && attrs.layoutInDisplayCutoutMode
- != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
- // A non-translucent main window of the app enforced to go edge-to-edge
- // isn't allowed to fit display cutout, or it will cause software bezels.
- throw new IllegalArgumentException("Illegal attributes: Main window of "
- + win.mActivityRecord.getName() + " that isn't translucent and"
- + " targets SDK level " + win.mActivityRecord.mTargetSdk
- + " (>= 35) trying to specify layoutInDisplayCutoutMode as '"
- + WindowManager.LayoutParams.layoutInDisplayCutoutModeToString(
- attrs.layoutInDisplayCutoutMode)
- + "' instead of 'always'");
- }
}
break;
}
@@ -1939,6 +1925,11 @@ public class DisplayPolicy {
*/
final Rect mOverrideConfigInsets = new Rect();
+ /**
+ * Override value of mNonDecorInsets for app compatibility purpose.
+ */
+ final Rect mOverrideNonDecorInsets = new Rect();
+
/** The display frame available after excluding {@link #mNonDecorInsets}. */
final Rect mNonDecorFrame = new Rect();
@@ -1954,6 +1945,11 @@ public class DisplayPolicy {
*/
final Rect mOverrideConfigFrame = new Rect();
+ /**
+ * Override value of mNonDecorFrame for app compatibility purpose.
+ */
+ final Rect mOverrideNonDecorFrame = new Rect();
+
private boolean mNeedUpdate = true;
InsetsState update(DisplayContent dc, int rotation, int w, int h) {
@@ -1973,17 +1969,26 @@ public class DisplayPolicy {
? configInsets
: insetsState.calculateInsets(displayFrame,
dc.mWmService.mOverrideConfigTypes, true /* ignoreVisibility */);
+ final Insets overrideDecorInsets = dc.mWmService.mDecorTypes
+ == dc.mWmService.mOverrideDecorTypes
+ ? decor
+ : insetsState.calculateInsets(displayFrame,
+ dc.mWmService.mOverrideDecorTypes, true /* ignoreVisibility */);
mNonDecorInsets.set(decor.left, decor.top, decor.right, decor.bottom);
mConfigInsets.set(configInsets.left, configInsets.top, configInsets.right,
configInsets.bottom);
mOverrideConfigInsets.set(overrideConfigInsets.left, overrideConfigInsets.top,
overrideConfigInsets.right, overrideConfigInsets.bottom);
+ mOverrideNonDecorInsets.set(overrideDecorInsets.left, overrideDecorInsets.top,
+ overrideDecorInsets.right, overrideDecorInsets.bottom);
mNonDecorFrame.set(displayFrame);
mNonDecorFrame.inset(mNonDecorInsets);
mConfigFrame.set(displayFrame);
mConfigFrame.inset(mConfigInsets);
mOverrideConfigFrame.set(displayFrame);
mOverrideConfigFrame.inset(mOverrideConfigInsets);
+ mOverrideNonDecorFrame.set(displayFrame);
+ mOverrideNonDecorFrame.inset(mOverrideNonDecorInsets);
mNeedUpdate = false;
return insetsState;
}
@@ -1992,9 +1997,11 @@ public class DisplayPolicy {
mNonDecorInsets.set(other.mNonDecorInsets);
mConfigInsets.set(other.mConfigInsets);
mOverrideConfigInsets.set(other.mOverrideConfigInsets);
+ mOverrideNonDecorInsets.set(other.mOverrideNonDecorInsets);
mNonDecorFrame.set(other.mNonDecorFrame);
mConfigFrame.set(other.mConfigFrame);
mOverrideConfigFrame.set(other.mOverrideConfigFrame);
+ mOverrideNonDecorFrame.set(other.mOverrideNonDecorFrame);
mNeedUpdate = false;
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index f220c9d06e14..cb5ad910c651 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -1308,7 +1308,8 @@ final class LetterboxUiController {
}
final boolean shouldShowLetterboxUi =
- (mActivityRecord.isInLetterboxAnimation() || isSurfaceVisible(mainWindow))
+ (mActivityRecord.isInLetterboxAnimation() || mActivityRecord.isVisible()
+ || mActivityRecord.isVisibleRequested())
&& mainWindow.areAppWindowBoundsLetterboxed()
// Check for FLAG_SHOW_WALLPAPER explicitly instead of using
// WindowContainer#showWallpaper because the later will return true when this
@@ -1320,12 +1321,6 @@ final class LetterboxUiController {
return shouldShowLetterboxUi;
}
- @VisibleForTesting
- boolean isSurfaceVisible(WindowState mainWindow) {
- return mainWindow.isOnScreen() && (mActivityRecord.isVisible()
- || mActivityRecord.isVisibleRequested());
- }
-
private Color getLetterboxBackgroundColor() {
final WindowState w = mActivityRecord.findMainWindow();
if (w == null || w.isLetterboxedForDisplayCutout()) {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 218fb7f6b817..804201088c59 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2188,38 +2188,20 @@ class TaskFragment extends WindowContainer<WindowContainer> {
return getTask() != null ? getTask().mTaskId : INVALID_TASK_ID;
}
- void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
- @NonNull Configuration parentConfig) {
- computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
- null /* compatInsets */);
+ static class ConfigOverrideHint {
+ @Nullable DisplayInfo mTmpOverrideDisplayInfo;
+ @Nullable ActivityRecord.CompatDisplayInsets mTmpCompatInsets;
+ boolean mUseLegacyInsetsForStableBounds;
}
void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
- @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo) {
- if (overrideDisplayInfo != null) {
- // Make sure the screen related configs can be computed by the provided display info.
- inOutConfig.screenLayout = Configuration.SCREENLAYOUT_UNDEFINED;
- invalidateAppBoundsConfig(inOutConfig);
- }
- computeConfigResourceOverrides(inOutConfig, parentConfig, overrideDisplayInfo,
- null /* compatInsets */);
- }
-
- void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
- @NonNull Configuration parentConfig,
- @Nullable ActivityRecord.CompatDisplayInsets compatInsets) {
- if (compatInsets != null) {
- // Make sure the app bounds can be computed by the compat insets.
- invalidateAppBoundsConfig(inOutConfig);
- }
- computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
- compatInsets);
+ @NonNull Configuration parentConfig) {
+ computeConfigResourceOverrides(inOutConfig, parentConfig, null /* configOverrideHint */);
}
/**
* Forces the app bounds related configuration can be computed by
- * {@link #computeConfigResourceOverrides(Configuration, Configuration, DisplayInfo,
- * ActivityRecord.CompatDisplayInsets)}.
+ * {@link #computeConfigResourceOverrides(Configuration, Configuration, ConfigOverrideHint)}.
*/
private static void invalidateAppBoundsConfig(@NonNull Configuration inOutConfig) {
final Rect appBounds = inOutConfig.windowConfiguration.getAppBounds();
@@ -2239,8 +2221,24 @@ class TaskFragment extends WindowContainer<WindowContainer> {
* just be inherited from the parent configuration.
**/
void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
- @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo,
- @Nullable ActivityRecord.CompatDisplayInsets compatInsets) {
+ @NonNull Configuration parentConfig, @Nullable ConfigOverrideHint overrideHint) {
+ DisplayInfo overrideDisplayInfo = null;
+ ActivityRecord.CompatDisplayInsets compatInsets = null;
+ boolean useLegacyInsetsForStableBounds = false;
+ if (overrideHint != null) {
+ overrideDisplayInfo = overrideHint.mTmpOverrideDisplayInfo;
+ compatInsets = overrideHint.mTmpCompatInsets;
+ useLegacyInsetsForStableBounds = overrideHint.mUseLegacyInsetsForStableBounds;
+ if (overrideDisplayInfo != null) {
+ // Make sure the screen related configs can be computed by the provided
+ // display info.
+ inOutConfig.screenLayout = Configuration.SCREENLAYOUT_UNDEFINED;
+ }
+ if (overrideDisplayInfo != null || compatInsets != null) {
+ // Make sure the app bounds can be computed by the compat insets.
+ invalidateAppBoundsConfig(inOutConfig);
+ }
+ }
int windowingMode = inOutConfig.windowConfiguration.getWindowingMode();
if (windowingMode == WINDOWING_MODE_UNDEFINED) {
windowingMode = parentConfig.windowConfiguration.getWindowingMode();
@@ -2309,7 +2307,8 @@ class TaskFragment extends WindowContainer<WindowContainer> {
// area, i.e. the screen area without the system bars.
// The non decor inset are areas that could never be removed in Honeycomb. See
// {@link WindowManagerPolicy#getNonDecorInsetsLw}.
- calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di);
+ calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di,
+ useLegacyInsetsForStableBounds);
} else {
// Apply the given non-decor and stable insets to calculate the corresponding bounds
// for screen size of configuration.
@@ -2407,9 +2406,11 @@ class TaskFragment extends WindowContainer<WindowContainer> {
* @param outNonDecorBounds where to place bounds with non-decor insets applied.
* @param outStableBounds where to place bounds with stable insets applied.
* @param bounds the bounds to inset.
+ * @param useLegacyInsetsForStableBounds {@code true} if we need to use the legacy insets frame
+ * for apps targeting U or before when calculating stable bounds.
*/
void calculateInsetFrames(Rect outNonDecorBounds, Rect outStableBounds, Rect bounds,
- DisplayInfo displayInfo) {
+ DisplayInfo displayInfo, boolean useLegacyInsetsForStableBounds) {
outNonDecorBounds.set(bounds);
outStableBounds.set(bounds);
if (mDisplayContent == null) {
@@ -2420,8 +2421,13 @@ class TaskFragment extends WindowContainer<WindowContainer> {
final DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
final DisplayPolicy.DecorInsets.Info info = policy.getDecorInsetsInfo(
displayInfo.rotation, displayInfo.logicalWidth, displayInfo.logicalHeight);
- intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, info.mNonDecorInsets);
- intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mConfigInsets);
+ if (!useLegacyInsetsForStableBounds) {
+ intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mConfigInsets);
+ intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, info.mNonDecorInsets);
+ } else {
+ intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mOverrideConfigInsets);
+ intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, info.mOverrideNonDecorInsets);
+ }
}
/**
diff --git a/services/core/java/com/android/server/wm/UnsupportedCompileSdkDialog.java b/services/core/java/com/android/server/wm/UnsupportedCompileSdkDialog.java
index f376e8b1f9ed..0655068aae86 100644
--- a/services/core/java/com/android/server/wm/UnsupportedCompileSdkDialog.java
+++ b/services/core/java/com/android/server/wm/UnsupportedCompileSdkDialog.java
@@ -32,8 +32,8 @@ import com.android.server.utils.AppInstallerUtil;
class UnsupportedCompileSdkDialog extends AppWarnings.BaseDialog {
UnsupportedCompileSdkDialog(final AppWarnings manager, Context context,
- ApplicationInfo appInfo) {
- super(manager, appInfo.packageName);
+ ApplicationInfo appInfo, int userId) {
+ super(manager, context, appInfo.packageName, userId);
final PackageManager pm = context.getPackageManager();
final CharSequence label = appInfo.loadSafeLabel(pm,
@@ -68,6 +68,6 @@ class UnsupportedCompileSdkDialog extends AppWarnings.BaseDialog {
final CheckBox alwaysShow = mDialog.findViewById(R.id.ask_checkbox);
alwaysShow.setChecked(true);
alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> manager.setPackageFlag(
- mPackageName, AppWarnings.FLAG_HIDE_COMPILE_SDK, !isChecked));
+ mUserId, mPackageName, AppWarnings.FLAG_HIDE_COMPILE_SDK, !isChecked));
}
}
diff --git a/services/core/java/com/android/server/wm/UnsupportedDisplaySizeDialog.java b/services/core/java/com/android/server/wm/UnsupportedDisplaySizeDialog.java
index b11c22de4286..5e40d9c3e1b0 100644
--- a/services/core/java/com/android/server/wm/UnsupportedDisplaySizeDialog.java
+++ b/services/core/java/com/android/server/wm/UnsupportedDisplaySizeDialog.java
@@ -30,8 +30,8 @@ import com.android.internal.R;
class UnsupportedDisplaySizeDialog extends AppWarnings.BaseDialog {
UnsupportedDisplaySizeDialog(final AppWarnings manager, Context context,
- ApplicationInfo appInfo) {
- super(manager, appInfo.packageName);
+ ApplicationInfo appInfo, int userId) {
+ super(manager, context, appInfo.packageName, userId);
final PackageManager pm = context.getPackageManager();
final CharSequence label = appInfo.loadSafeLabel(pm,
@@ -59,6 +59,6 @@ class UnsupportedDisplaySizeDialog extends AppWarnings.BaseDialog {
final CheckBox alwaysShow = mDialog.findViewById(R.id.ask_checkbox);
alwaysShow.setChecked(true);
alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> manager.setPackageFlag(
- mPackageName, AppWarnings.FLAG_HIDE_DISPLAY_SIZE, !isChecked));
+ mUserId, mPackageName, AppWarnings.FLAG_HIDE_DISPLAY_SIZE, !isChecked));
}
}
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 5c24eee63317..9d1551c12216 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -193,11 +193,11 @@ class WallpaperWindowToken extends WindowToken {
if (mVisibleRequested != visible) {
// Before setting mVisibleRequested so we can track changes.
final WindowState wpTarget = mDisplayContent.mWallpaperController.getWallpaperTarget();
- final boolean isTargetNotCollectedActivity = wpTarget != null
- && wpTarget.mActivityRecord != null
- && !mTransitionController.isCollecting(wpTarget.mActivityRecord);
- // Skip collecting requesting-invisible wallpaper if the wallpaper target is an activity
- // and it is not collected. Because the visibility change may be called after the
+ final boolean isTargetNotCollectedActivity = wpTarget == null
+ || (wpTarget.mActivityRecord != null
+ && !mTransitionController.isCollecting(wpTarget.mActivityRecord));
+ // Skip collecting requesting-invisible wallpaper if the wallpaper target is empty or
+ // a non-collected activity. Because the visibility change may be called after the
// transition of activity is finished, e.g. WallpaperController#hideWallpapers from
// hiding surface of the target. Then if there is a next transition, the wallpaper
// change may be collected into the unrelated transition and cause a weird animation.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 143605ac7320..1496ae02427f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -570,6 +570,8 @@ public class WindowManagerService extends IWindowManager.Stub
final int mOverrideConfigTypes;
+ final int mOverrideDecorTypes;
+
final boolean mLimitedAlphaCompositing;
final int mMaxUiWidth;
@@ -1255,10 +1257,13 @@ public class WindowManagerService extends IWindowManager.Stub
if (isScreenSizeDecoupledFromStatusBarAndCutout && !mFlags.mInsetsDecoupledConfiguration) {
// If the global new behavior is not there, but the partial decouple flag is on.
mOverrideConfigTypes = 0;
+ mOverrideDecorTypes = 0;
} else {
mOverrideConfigTypes =
WindowInsets.Type.displayCutout() | WindowInsets.Type.statusBars()
| WindowInsets.Type.navigationBars();
+ mOverrideDecorTypes = WindowInsets.Type.displayCutout()
+ | WindowInsets.Type.navigationBars();
}
mLetterboxConfiguration = new LetterboxConfiguration(
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index b999305fbee0..736b05189a72 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -1,12 +1,8 @@
-# Display
-per-file com_android_server_lights_LightsService.cpp = michaelwr@google.com, santoscordon@google.com
-
# Input
per-file com_android_server_input_* = file:/INPUT_OWNERS
# Power
-per-file com_android_server_HardwarePropertiesManagerService.cpp = michaelwr@google.com, santoscordon@google.com
-per-file com_android_server_power_PowerManagerService.* = michaelwr@google.com, santoscordon@google.com
+per-file com_android_server_HardwarePropertiesManagerService.cpp = file:/services/core/java/com/android/server/power/OWNERS
# BatteryStats
per-file com_android_server_am_BatteryStatsService.cpp = file:/BATTERY_STATS_OWNERS
@@ -16,6 +12,7 @@ per-file com_android_server_SystemClock* = file:/services/core/java/com/android/
per-file com_android_server_Usb* = file:/services/usb/OWNERS
per-file com_android_server_Vibrator* = file:/services/core/java/com/android/server/vibrator/OWNERS
per-file com_android_server_accessibility_* = file:/services/accessibility/OWNERS
+per-file com_android_server_display_* = file:/services/core/java/com/android/server/display/OWNERS
per-file com_android_server_hdmi_* = file:/core/java/android/hardware/hdmi/OWNERS
per-file com_android_server_lights_* = file:/services/core/java/com/android/server/lights/OWNERS
per-file com_android_server_location_* = file:/location/java/android/location/OWNERS
diff --git a/services/core/jni/com_android_server_utils_AnrTimer.cpp b/services/core/jni/com_android_server_utils_AnrTimer.cpp
index 8ca5333a65bd..da95666fb7c4 100644
--- a/services/core/jni/com_android_server_utils_AnrTimer.cpp
+++ b/services/core/jni/com_android_server_utils_AnrTimer.cpp
@@ -487,7 +487,6 @@ class AnrTimerService::Ticker {
timer_id_t front = headTimerId();
auto found = running_.find(key);
if (found != running_.end()) running_.erase(found);
- if (front != headTimerId()) restartLocked();
}
// Remove every timer associated with the service.
@@ -501,7 +500,6 @@ class AnrTimerService::Ticker {
i++;
}
}
- if (front != headTimerId()) restartLocked();
}
// Return the number of timers still running.
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index cb637579d8db..7e083ba8859a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -107,6 +107,8 @@ import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOT
import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_HIBERNATION;
import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS;
import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_SUSPENSION;
+import static android.app.AppOpsManager.OP_RUN_ANY_IN_BACKGROUND;
+import static android.app.AppOpsManager.OP_RUN_IN_BACKGROUND;
import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_AFFILIATED;
import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
@@ -886,10 +888,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
"enable_permission_based_access";
private static final boolean DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG = false;
- // TODO(b/266831522) remove the flag after rollout.
- private static final String APPLICATION_EXEMPTIONS_FLAG = "application_exemptions";
- private static final boolean DEFAULT_APPLICATION_EXEMPTIONS_FLAG = true;
-
private static final int RETRY_COPY_ACCOUNT_ATTEMPTS = 3;
/**
@@ -3689,26 +3687,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
mDevicePolicyEngine.handleStartUser(userId);
}
- void pushUserControlDisabledPackagesLocked(int userId) {
- final int targetUserId;
- final ActiveAdmin owner;
- if (getDeviceOwnerUserIdUncheckedLocked() == userId) {
- owner = getDeviceOwnerAdminLocked();
- targetUserId = UserHandle.USER_ALL;
- } else {
- owner = getProfileOwnerAdminLocked(userId);
- targetUserId = userId;
- }
-
- List<String> protectedPackages = (owner == null || owner.protectedPackages == null)
- ? null : owner.protectedPackages;
- mInjector.binderWithCleanCallingIdentity(() ->
- mInjector.getPackageManagerInternal().setOwnerProtectedPackages(
- targetUserId, protectedPackages));
- mUsageStatsManagerInternal.setAdminProtectedPackages(new ArraySet(protectedPackages),
- targetUserId);
- }
-
void handleUnlockUser(int userId) {
startOwnerService(userId, "unlock-user");
mDevicePolicyEngine.handleUnlockUser(userId);
@@ -3957,7 +3935,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (policy.mPasswordOwner == oldAdminUid) {
policy.mPasswordOwner = adminToTransfer.getUid();
}
-
+ transferSubscriptionOwnership(outgoingReceiver, incomingReceiver);
saveSettingsLocked(userHandle);
sendAdminCommandLocked(adminToTransfer, DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED,
null, null);
@@ -15913,14 +15891,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
@Override
- public boolean isApplicationExemptionsFlagEnabled() {
- return DeviceConfig.getBoolean(
- NAMESPACE_DEVICE_POLICY_MANAGER,
- APPLICATION_EXEMPTIONS_FLAG,
- DEFAULT_APPLICATION_EXEMPTIONS_FLAG);
- }
-
- @Override
public List<Bundle> getApplicationRestrictionsPerAdminForUser(
String packageName, @UserIdInt int userId) {
if (UserHandle.getCallingUserId() != userId
@@ -19501,6 +19471,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
.write();
}
+ private void transferSubscriptionOwnership(ComponentName admin, ComponentName target) {
+ if (Flags.esimManagementEnabled()) {
+ SubscriptionManager subscriptionManager = mContext.getSystemService(
+ SubscriptionManager.class);
+ for (int subId : getSubscriptionIdsInternal(admin.getPackageName()).toArray()) {
+ try {
+ subscriptionManager.setGroupOwner(subId, target.getPackageName());
+ } catch (Exception e) {
+ // Shouldn't happen.
+ Slogf.e(LOG_TAG, e, "Error setting group owner for subId: " + subId);
+ }
+ }
+ }
+ }
+
private void prepareTransfer(ComponentName admin, ComponentName target,
PersistableBundle bundle, int callingUserId, String adminType) {
saveTransferOwnershipBundleLocked(bundle, callingUserId);
@@ -20378,34 +20363,47 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
hasCallingOrSelfPermission(permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS));
final CallerIdentity caller = getCallerIdentity(callerPackage);
- final ApplicationInfo packageInfo;
- packageInfo = getPackageInfoWithNullCheck(packageName, caller);
+ final AppOpsManager appOpsMgr = mInjector.getAppOpsManager();
+ final ApplicationInfo appInfo = getPackageInfoWithNullCheck(packageName, caller);
+ final int uid = appInfo.uid;
- for (Map.Entry<Integer, String> entry :
- APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.entrySet()) {
- int currentMode = mInjector.getAppOpsManager().unsafeCheckOpNoThrow(
- entry.getValue(), packageInfo.uid, packageInfo.packageName);
- int newMode = ArrayUtils.contains(exemptions, entry.getKey())
- ? MODE_ALLOWED : MODE_DEFAULT;
- mInjector.binderWithCleanCallingIdentity(() -> {
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.forEach((exemption, appOp) -> {
+ int currentMode = appOpsMgr.unsafeCheckOpNoThrow(appOp, uid, packageName);
+ int newMode = ArrayUtils.contains(exemptions, exemption)
+ ? MODE_ALLOWED : MODE_DEFAULT;
if (currentMode != newMode) {
- mInjector.getAppOpsManager()
- .setMode(entry.getValue(),
- packageInfo.uid,
- packageName,
- newMode);
+ appOpsMgr.setMode(appOp, uid, packageName, newMode);
+
+ // If the user has already disabled background usage for the package, it won't
+ // have OP_RUN_ANY_IN_BACKGROUND app op and won't execute in the background. The
+ // code below grants that app op, and once the exemption is in place, the user
+ // won't be able to disable background usage anymore.
+ if (Flags.powerExemptionBgUsageFix()
+ && exemption == EXEMPT_FROM_POWER_RESTRICTIONS
+ && newMode == MODE_ALLOWED) {
+ setBgUsageAppOp(appOpsMgr, appInfo);
+ }
}
});
- }
+ });
+
String[] appOpExemptions = new String[exemptions.length];
for (int i = 0; i < exemptions.length; i++) {
appOpExemptions[i] = APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.get(exemptions[i]);
}
DevicePolicyEventLogger
- .createEvent(DevicePolicyEnums.SET_APPLICATION_EXEMPTIONS)
- .setAdmin(caller.getPackageName())
- .setStrings(packageName, appOpExemptions)
- .write();
+ .createEvent(DevicePolicyEnums.SET_APPLICATION_EXEMPTIONS)
+ .setAdmin(caller.getPackageName())
+ .setStrings(packageName, appOpExemptions)
+ .write();
+ }
+
+ static void setBgUsageAppOp(AppOpsManager appOpsMgr, ApplicationInfo appInfo) {
+ appOpsMgr.setMode(OP_RUN_ANY_IN_BACKGROUND, appInfo.uid, appInfo.packageName, MODE_ALLOWED);
+ if (appInfo.targetSdkVersion < Build.VERSION_CODES.O) {
+ appOpsMgr.setMode(OP_RUN_IN_BACKGROUND, appInfo.uid, appInfo.packageName, MODE_ALLOWED);
+ }
}
@Override
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index 42ac998bf96c..d02cfee72aa2 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -357,7 +357,8 @@ class OwnersData {
@Override
boolean shouldWrite() {
- return (mDeviceOwner != null) || (mSystemUpdatePolicy != null)
+ return Flags.alwaysPersistDo()
+ || (mDeviceOwner != null) || (mSystemUpdatePolicy != null)
|| (mSystemUpdateInfo != null);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index e713a827fc76..7a9fa0fb5658 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -163,8 +163,7 @@ final class PolicyDefinition<V> {
new NoArgsPolicyKey(
DevicePolicyIdentifiers.USER_CONTROL_DISABLED_PACKAGES_POLICY),
new StringSetUnion(),
- (Set<String> value, Context context, Integer userId, PolicyKey policyKey) ->
- PolicyEnforcerCallbacks.setUserControlDisabledPackages(value, userId),
+ PolicyEnforcerCallbacks::setUserControlDisabledPackages,
new StringSetPolicySerializer());
// This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index a7adc5b6d925..a0d9be549880 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.AppGlobals;
+import android.app.AppOpsManager;
import android.app.admin.DevicePolicyCache;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
@@ -29,6 +30,7 @@ import android.app.admin.PackagePermissionPolicyKey;
import android.app.admin.PackagePolicyKey;
import android.app.admin.PolicyKey;
import android.app.admin.UserRestrictionPolicyKey;
+import android.app.admin.flags.Flags;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.Context;
@@ -37,6 +39,7 @@ import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.os.Binder;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -183,15 +186,31 @@ final class PolicyEnforcerCallbacks {
}
static boolean setUserControlDisabledPackages(
- @Nullable Set<String> packages, int userId) {
+ @Nullable Set<String> packages, Context context, int userId, PolicyKey policyKey) {
Binder.withCleanCallingIdentity(() -> {
- LocalServices.getService(PackageManagerInternal.class)
- .setOwnerProtectedPackages(
- userId,
- packages == null ? null : packages.stream().toList());
+ PackageManagerInternal pmi =
+ LocalServices.getService(PackageManagerInternal.class);
+ pmi.setOwnerProtectedPackages(userId,
+ packages == null ? null : packages.stream().toList());
LocalServices.getService(UsageStatsManagerInternal.class)
- .setAdminProtectedPackages(
+ .setAdminProtectedPackages(
packages == null ? null : new ArraySet<>(packages), userId);
+
+ if (Flags.disallowUserControlBgUsageFix()) {
+ if (packages == null) {
+ return;
+ }
+ final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+ for (var pkg : packages) {
+ final var appInfo = pmi.getApplicationInfo(pkg,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+ Process.myUid(), userId);
+ if (appInfo != null) {
+ DevicePolicyManagerService.setBgUsageAppOp(appOpsManager, appInfo);
+ }
+ }
+ }
});
return true;
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
index fe7bbe0ecf4b..ea08be4f1be4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
@@ -157,7 +157,6 @@ public class ExternalDisplayPolicyTest {
verify(mMockedLogicalDisplayMapper, never()).setDisplayEnabledLocked(any(), anyBoolean());
verify(mMockedDisplayNotificationManager, times(2))
.onHighTemperatureExternalDisplayNotAllowed();
- verify(mMockedInjector, never()).onExternalDisplayReadyToBeEnabled(anyInt());
}
@Test
@@ -168,7 +167,6 @@ public class ExternalDisplayPolicyTest {
verify(mMockedLogicalDisplayMapper, never()).setDisplayEnabledLocked(any(), anyBoolean());
verify(mMockedDisplayNotificationManager, never())
.onHighTemperatureExternalDisplayNotAllowed();
- verify(mMockedInjector, never()).onExternalDisplayReadyToBeEnabled(anyInt());
}
@Test
@@ -186,7 +184,6 @@ public class ExternalDisplayPolicyTest {
// Expected only 1 invocation, upon critical temperature.
verify(mMockedDisplayNotificationManager).onHighTemperatureExternalDisplayNotAllowed();
verify(mMockedExternalDisplayStatsService).onDisplayDisabled(eq(EXTERNAL_DISPLAY_ID));
- verify(mMockedInjector, never()).onExternalDisplayReadyToBeEnabled(anyInt());
}
@Test
@@ -194,7 +191,6 @@ public class ExternalDisplayPolicyTest {
mExternalDisplayPolicy.setExternalDisplayEnabledLocked(mMockedLogicalDisplay,
/*enabled=*/ true);
assertDisplaySetEnabled(/*enabled=*/ true);
- verify(mMockedInjector).onExternalDisplayReadyToBeEnabled(eq(EXTERNAL_DISPLAY_ID));
}
@Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index 040ae96d902e..4591d91ceea7 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -1952,7 +1952,7 @@ public class DisplayModeDirectorTest {
SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
votesByDisplay.put(DISPLAY_ID_2, votes);
- director.getDisplayObserver().onExternalDisplayReadyToBeEnabled(DISPLAY_ID_2);
+ director.getDisplayObserver().onDisplayAdded(DISPLAY_ID_2);
director.injectVotesByDisplay(votesByDisplay);
var desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID_2);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
index 7cfc1dd8db65..2d317af3d85d 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
@@ -38,6 +38,7 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Resources;
+import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
import android.os.Handler;
import android.os.Looper;
@@ -108,7 +109,7 @@ public class DisplayObserverTest {
private Context mContext;
private DisplayModeDirector.Injector mInjector;
private Handler mHandler;
- private DisplayModeDirector.DisplayObserver mObserver;
+ private DisplayManager.DisplayListener mObserver;
private Resources mResources;
@Mock
private DisplayManagerFlags mDisplayManagerFlags;
@@ -162,7 +163,6 @@ public class DisplayObserverTest {
.isEqualTo(null);
// Testing that the vote is not added when display is added because feature is disabled
- mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY);
mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
.isEqualTo(null);
@@ -196,7 +196,6 @@ public class DisplayObserverTest {
init();
assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
.isEqualTo(null);
- mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY);
mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
.isEqualTo(null);
@@ -248,7 +247,6 @@ public class DisplayObserverTest {
init();
assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
.isEqualTo(null);
- mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY);
mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
.isEqualTo(null);
@@ -281,7 +279,6 @@ public class DisplayObserverTest {
init();
assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
.isEqualTo(null);
- mObserver.onExternalDisplayReadyToBeEnabled(DEFAULT_DISPLAY);
mObserver.onDisplayAdded(DEFAULT_DISPLAY);
assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE))
.isEqualTo(expectedResolutionVote);
@@ -303,7 +300,6 @@ public class DisplayObserverTest {
.thenReturn(MAX_HEIGHT);
init();
assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null);
- mObserver.onExternalDisplayReadyToBeEnabled(DEFAULT_DISPLAY);
mObserver.onDisplayAdded(DEFAULT_DISPLAY);
assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null);
assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null);
@@ -323,7 +319,6 @@ public class DisplayObserverTest {
.thenReturn(MAX_HEIGHT);
init();
assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null);
- mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY);
mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null);
assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(
@@ -343,7 +338,6 @@ public class DisplayObserverTest {
when(mDisplayManagerFlags.isExternalDisplayLimitModeEnabled()).thenReturn(true);
init();
assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null);
- mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY);
mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null);
assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null);
@@ -366,7 +360,6 @@ public class DisplayObserverTest {
.thenReturn(true);
init();
assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null);
- mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY);
mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(
Vote.forPhysicalRefreshRates(
@@ -390,7 +383,6 @@ public class DisplayObserverTest {
.thenReturn(true);
init();
assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null);
- mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY);
mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null);
}
@@ -405,7 +397,6 @@ public class DisplayObserverTest {
when(mDisplayManagerFlags.isDisplaysRefreshRatesSynchronizationEnabled()).thenReturn(true);
init();
assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null);
- mObserver.onExternalDisplayReadyToBeEnabled(EXTERNAL_DISPLAY);
mObserver.onDisplayAdded(EXTERNAL_DISPLAY);
assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
index 7d3a1103a044..c1f4feee9c57 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
@@ -321,8 +321,7 @@ public class ApplicationStartInfoTest {
app1PackageName); // packageName
ServiceRecord service = ServiceRecord.newEmptyInstanceForTest(mAms);
- mAppStartInfoTracker.handleProcessServiceStart(appStartTimestampService, app, service,
- false);
+ mAppStartInfoTracker.handleProcessServiceStart(appStartTimestampService, app, service);
list.clear();
mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, 0, 0, list);
assertEquals(list.size(), 2);
@@ -336,7 +335,7 @@ public class ApplicationStartInfoTest {
app1ProcessName, // processName
ApplicationStartInfo.START_REASON_SERVICE, // reason
ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state
- ApplicationStartInfo.START_TYPE_WARM, // state type
+ ApplicationStartInfo.START_TYPE_COLD, // state type
ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
// Case 5: Create an instance of app1 with a different user started for a broadcast
@@ -350,7 +349,7 @@ public class ApplicationStartInfoTest {
app1PackageName); // packageName
mAppStartInfoTracker.handleProcessBroadcastStart(appStartTimestampBroadcast, app,
- null, true /* isColdStart */);
+ buildIntent(COMPONENT), false /* isAlarm */);
list.clear();
mAppStartInfoTracker.getStartInfo(app1PackageName, app1UidUser2, app1PidUser2, 0, list);
assertEquals(list.size(), 1);
@@ -395,7 +394,7 @@ public class ApplicationStartInfoTest {
app2PackageName); // packageName
mAppStartInfoTracker.handleProcessContentProviderStart(appStartTimestampRContentProvider,
- app, false);
+ app);
list.clear();
mAppStartInfoTracker.getStartInfo(app2PackageName, app2UidUser2, app2PidUser2, 0, list);
assertEquals(list.size(), 1);
@@ -409,7 +408,7 @@ public class ApplicationStartInfoTest {
app2ProcessName, // processName
ApplicationStartInfo.START_REASON_CONTENT_PROVIDER, // reason
ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state
- ApplicationStartInfo.START_TYPE_WARM, // state type
+ ApplicationStartInfo.START_TYPE_COLD, // state type
ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
// Case 8: Save and load again
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
index ce281daf41c4..586191702578 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
@@ -112,6 +112,9 @@ public abstract class BaseBroadcastQueueTest {
@Mock
ProcessList mProcessList;
+ @Mock
+ AppStartInfoTracker mAppStartInfoTracker;
+
Context mContext;
ActivityManagerService mAms;
BroadcastConstants mConstants;
@@ -172,6 +175,8 @@ public abstract class BaseBroadcastQueueTest {
mSkipPolicy = spy(new BroadcastSkipPolicy(mAms));
doReturn(null).when(mSkipPolicy).shouldSkipMessage(any(), any());
doReturn(false).when(mSkipPolicy).disallowBackgroundStart(any());
+
+ doReturn(mAppStartInfoTracker).when(mProcessList).getAppStartInfoTracker();
}
public void tearDown() throws Exception {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 97ae0bd6c7f7..13ba1e5e705c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -2393,6 +2393,20 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest {
assertNull(mAms.getProcessRecordLocked(PACKAGE_BLUE, getUidForPackage(PACKAGE_BLUE)));
}
+ @Test
+ public void testBroadcastAppStartInfoReported() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+
+ final Intent timezone = new Intent(Intent.ACTION_TIME_TICK);
+ enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN))));
+
+ waitForIdle();
+
+ verify(mAppStartInfoTracker, times(1)).handleProcessBroadcastStart(anyLong(), any(), any(),
+ anyBoolean());
+ }
+
private long getReceiverScheduledTime(@NonNull BroadcastRecord r, @NonNull Object receiver) {
for (int i = 0; i < r.receivers.size(); ++i) {
if (isReceiverEquals(receiver, r.receivers.get(i))) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 4c7a8fef6f0b..959078326f6b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -205,8 +205,6 @@ public class MockingOomAdjusterTests {
new ProcessStatsService(sService, new File(sContext.getFilesDir(), "procstats")));
setFieldValue(ActivityManagerService.class, sService, "mBackupTargets",
mock(SparseArray.class));
- setFieldValue(ActivityManagerService.class, sService, "mOomAdjProfiler",
- mock(OomAdjProfiler.class));
setFieldValue(ActivityManagerService.class, sService, "mUserController",
mock(UserController.class));
setFieldValue(ActivityManagerService.class, sService, "mAppProfiler", profiler);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 6df4907af93c..584fd6270c69 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -24,6 +24,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.job.Flags.FLAG_COUNT_QUOTA_FIX;
import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
@@ -75,6 +76,9 @@ import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.DeviceConfig;
import android.util.ArraySet;
import android.util.SparseBooleanArray;
@@ -98,6 +102,7 @@ import com.android.server.usage.AppStandbyInternal;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -154,6 +159,10 @@ public class QuotaControllerTest {
@Mock
private UsageStatsManagerInternal mUsageStatsManager;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
private JobStore mJobStore;
@Before
@@ -1978,7 +1987,7 @@ public class QuotaControllerTest {
}
@Test
- public void testIsWithinQuotaLocked_UnderDuration_OverJobCount() {
+ public void testIsWithinQuotaLocked_UnderDuration_OverJobCountRateLimitWindow() {
setDischarging();
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
@@ -2021,7 +2030,7 @@ public class QuotaControllerTest {
}
@Test
- public void testIsWithinQuotaLocked_OverDuration_OverJobCount() {
+ public void testIsWithinQuotaLocked_OverDuration_OverJobCountRateLimitWindow() {
setDischarging();
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
@@ -2167,6 +2176,74 @@ public class QuotaControllerTest {
}
@Test
+ @RequiresFlagsEnabled(FLAG_COUNT_QUOTA_FIX)
+ public void testIsWithinQuotaLocked_UnderDuration_OverJobCountInWindow() {
+ setDischarging();
+
+ JobStatus jobRunning = createJobStatus(
+ "testIsWithinQuotaLocked_UnderDuration_OverJobCountInWindow", 1);
+ JobStatus jobPending = createJobStatus(
+ "testIsWithinQuotaLocked_UnderDuration_OverJobCountInWindow", 2);
+ setStandbyBucket(WORKING_INDEX, jobRunning, jobPending);
+
+ setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 10);
+
+ long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 9), false);
+
+ final ExecutionStats stats;
+ synchronized (mQuotaController.mLock) {
+ stats = mQuotaController.getExecutionStatsLocked(
+ SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
+ assertTrue(mQuotaController
+ .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
+ assertEquals(10, stats.jobCountLimit);
+ assertEquals(9, stats.bgJobCountInWindow);
+ }
+
+ when(mJobSchedulerService.isCurrentlyRunningLocked(jobRunning)).thenReturn(true);
+ when(mJobSchedulerService.isCurrentlyRunningLocked(jobPending)).thenReturn(false);
+
+ InOrder inOrder = inOrder(mJobSchedulerService);
+ trackJobs(jobRunning, jobPending);
+ // UID in the background.
+ setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
+ // Start the job.
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobRunning);
+ }
+
+ advanceElapsedClock(MINUTE_IN_MILLIS);
+ // Wait for some extra time to allow for job processing.
+ ArraySet<JobStatus> expected = new ArraySet<>();
+ expected.add(jobPending);
+ inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
+ .onControllerStateChanged(eq(expected));
+
+ synchronized (mQuotaController.mLock) {
+ assertTrue(mQuotaController.isWithinQuotaLocked(jobRunning));
+ assertTrue(jobRunning.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertTrue(jobRunning.isReady());
+ assertFalse(mQuotaController.isWithinQuotaLocked(jobPending));
+ assertFalse(jobPending.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
+ assertFalse(jobPending.isReady());
+ assertEquals(10, stats.bgJobCountInWindow);
+ }
+
+ advanceElapsedClock(MINUTE_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(jobRunning, null);
+ }
+
+ synchronized (mQuotaController.mLock) {
+ assertFalse(mQuotaController
+ .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
+ assertEquals(10, stats.bgJobCountInWindow);
+ }
+ }
+
+ @Test
public void testIsWithinQuotaLocked_TimingSession() {
setDischarging();
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
@@ -4651,7 +4728,7 @@ public class QuotaControllerTest {
// Handler is told to check when the quota will be consumed, not when the initial
// remaining time is over.
verify(handler, atLeast(1)).sendMessageDelayed(
- argThat(msg -> msg.what == QuotaController.MSG_REACHED_QUOTA),
+ argThat(msg -> msg.what == QuotaController.MSG_REACHED_TIME_QUOTA),
eq(10 * SECOND_IN_MILLIS));
verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs));
@@ -6618,7 +6695,7 @@ public class QuotaControllerTest {
// Handler is told to check when the quota will be consumed, not when the initial
// remaining time is over.
verify(handler, atLeast(1)).sendMessageDelayed(
- argThat(msg -> msg.what == QuotaController.MSG_REACHED_EJ_QUOTA),
+ argThat(msg -> msg.what == QuotaController.MSG_REACHED_EJ_TIME_QUOTA),
eq(10 * SECOND_IN_MILLIS));
verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs));
}
diff --git a/services/tests/selinux/Android.bp b/services/tests/selinux/Android.bp
index f38723854d6b..12a70387affd 100644
--- a/services/tests/selinux/Android.bp
+++ b/services/tests/selinux/Android.bp
@@ -52,6 +52,7 @@ android_test {
"androidx.test.ext.junit",
"androidx.test.ext.truth",
"androidx.test.runner",
+ "compatibility-device-util-axt",
"services.core",
],
test_suites: [
diff --git a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java
index b36c9bdaf456..e86108d84538 100644
--- a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java
+++ b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java
@@ -15,98 +15,144 @@
*/
package com.android.server.selinux;
-import static com.android.server.selinux.SelinuxAuditLogBuilder.PATH_MATCHER;
-import static com.android.server.selinux.SelinuxAuditLogBuilder.SCONTEXT_MATCHER;
-import static com.android.server.selinux.SelinuxAuditLogBuilder.TCONTEXT_MATCHER;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import static com.android.server.selinux.SelinuxAuditLogBuilder.toCategories;
import static com.google.common.truth.Truth.assertThat;
+import android.provider.DeviceConfig;
+
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.server.selinux.SelinuxAuditLogBuilder.SelinuxAuditLog;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.regex.Matcher;
+
@RunWith(AndroidJUnit4.class)
public class SelinuxAuditLogsBuilderTest {
- private final SelinuxAuditLogBuilder mAuditLogBuilder = new SelinuxAuditLogBuilder();
+ private static final String TEST_DOMAIN = "test_domain";
+
+ private SelinuxAuditLogBuilder mAuditLogBuilder;
+ private Matcher mScontextMatcher;
+ private Matcher mTcontextMatcher;
+ private Matcher mPathMatcher;
+
+ @Before
+ public void setUp() {
+ runWithShellPermissionIdentity(
+ () ->
+ DeviceConfig.setLocalOverride(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ SelinuxAuditLogBuilder.CONFIG_SELINUX_AUDIT_DOMAIN,
+ TEST_DOMAIN));
+
+ mAuditLogBuilder = new SelinuxAuditLogBuilder();
+ mScontextMatcher = mAuditLogBuilder.mScontextMatcher;
+ mTcontextMatcher = mAuditLogBuilder.mTcontextMatcher;
+ mPathMatcher = mAuditLogBuilder.mPathMatcher;
+ }
+
+ @After
+ public void tearDown() {
+ runWithShellPermissionIdentity(() -> DeviceConfig.clearAllLocalOverrides());
+ }
@Test
public void testMatcher_scontext() {
- assertThat(SCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0").matches()).isTrue();
- assertThat(SCONTEXT_MATCHER.group("stype")).isEqualTo("sdk_sandbox_audit");
- assertThat(SCONTEXT_MATCHER.group("scategories")).isNull();
+ assertThat(mScontextMatcher.reset("u:r:" + TEST_DOMAIN + ":s0").matches()).isTrue();
+ assertThat(mScontextMatcher.group("stype")).isEqualTo(TEST_DOMAIN);
+ assertThat(mScontextMatcher.group("scategories")).isNull();
- assertThat(SCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0:c123,c456").matches()).isTrue();
- assertThat(SCONTEXT_MATCHER.group("stype")).isEqualTo("sdk_sandbox_audit");
- assertThat(toCategories(SCONTEXT_MATCHER.group("scategories")))
+ assertThat(mScontextMatcher.reset("u:r:" + TEST_DOMAIN + ":s0:c123,c456").matches())
+ .isTrue();
+ assertThat(mScontextMatcher.group("stype")).isEqualTo(TEST_DOMAIN);
+ assertThat(toCategories(mScontextMatcher.group("scategories")))
.isEqualTo(new int[] {123, 456});
- assertThat(SCONTEXT_MATCHER.reset("u:r:not_sdk_sandbox:s0").matches()).isFalse();
- assertThat(SCONTEXT_MATCHER.reset("u:object_r:sdk_sandbox_audit:s0").matches()).isFalse();
- assertThat(SCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0:p123").matches()).isFalse();
+ assertThat(mScontextMatcher.reset("u:r:wrong_domain:s0").matches()).isFalse();
+ assertThat(mScontextMatcher.reset("u:object_r:" + TEST_DOMAIN + ":s0").matches()).isFalse();
+ assertThat(mScontextMatcher.reset("u:r:" + TEST_DOMAIN + ":s0:p123").matches()).isFalse();
}
@Test
public void testMatcher_tcontext() {
- assertThat(TCONTEXT_MATCHER.reset("u:object_r:target_type:s0").matches()).isTrue();
- assertThat(TCONTEXT_MATCHER.group("ttype")).isEqualTo("target_type");
- assertThat(TCONTEXT_MATCHER.group("tcategories")).isNull();
+ assertThat(mTcontextMatcher.reset("u:object_r:target_type:s0").matches()).isTrue();
+ assertThat(mTcontextMatcher.group("ttype")).isEqualTo("target_type");
+ assertThat(mTcontextMatcher.group("tcategories")).isNull();
- assertThat(TCONTEXT_MATCHER.reset("u:object_r:target_type2:s0:c666").matches()).isTrue();
- assertThat(TCONTEXT_MATCHER.group("ttype")).isEqualTo("target_type2");
- assertThat(toCategories(TCONTEXT_MATCHER.group("tcategories"))).isEqualTo(new int[] {666});
+ assertThat(mTcontextMatcher.reset("u:object_r:target_type2:s0:c666").matches()).isTrue();
+ assertThat(mTcontextMatcher.group("ttype")).isEqualTo("target_type2");
+ assertThat(toCategories(mTcontextMatcher.group("tcategories"))).isEqualTo(new int[] {666});
- assertThat(TCONTEXT_MATCHER.reset("u:r:target_type:s0").matches()).isFalse();
- assertThat(TCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0:x456").matches()).isFalse();
+ assertThat(mTcontextMatcher.reset("u:r:target_type:s0").matches()).isFalse();
+ assertThat(mTcontextMatcher.reset("u:r:" + TEST_DOMAIN + ":s0:x456").matches()).isFalse();
}
@Test
public void testMatcher_path() {
- assertThat(PATH_MATCHER.reset("\"/data\"").matches()).isTrue();
- assertThat(PATH_MATCHER.group("path")).isEqualTo("/data");
- assertThat(PATH_MATCHER.reset("\"/data/local\"").matches()).isTrue();
- assertThat(PATH_MATCHER.group("path")).isEqualTo("/data/local");
- assertThat(PATH_MATCHER.reset("\"/data/local/tmp\"").matches()).isTrue();
- assertThat(PATH_MATCHER.group("path")).isEqualTo("/data/local");
-
- assertThat(PATH_MATCHER.reset("\"/data/local").matches()).isFalse();
- assertThat(PATH_MATCHER.reset("\"_data_local\"").matches()).isFalse();
+ assertThat(mPathMatcher.reset("\"/data\"").matches()).isTrue();
+ assertThat(mPathMatcher.group("path")).isEqualTo("/data");
+ assertThat(mPathMatcher.reset("\"/data/local\"").matches()).isTrue();
+ assertThat(mPathMatcher.group("path")).isEqualTo("/data/local");
+ assertThat(mPathMatcher.reset("\"/data/local/tmp\"").matches()).isTrue();
+ assertThat(mPathMatcher.group("path")).isEqualTo("/data/local");
+
+ assertThat(mPathMatcher.reset("\"/data/local").matches()).isFalse();
+ assertThat(mPathMatcher.reset("\"_data_local\"").matches()).isFalse();
+ }
+
+ @Test
+ public void testMatcher_scontextDefaultConfig() {
+ runWithShellPermissionIdentity(
+ () ->
+ DeviceConfig.clearLocalOverride(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ SelinuxAuditLogBuilder.CONFIG_SELINUX_AUDIT_DOMAIN));
+
+ Matcher scontexMatcher = new SelinuxAuditLogBuilder().mScontextMatcher;
+
+ assertThat(scontexMatcher.reset("u:r:" + TEST_DOMAIN + ":s0").matches()).isFalse();
+ assertThat(scontexMatcher.reset("u:r:" + TEST_DOMAIN + ":s0:c123,c456").matches())
+ .isFalse();
+ assertThat(scontexMatcher.reset("u:r:wrong_domain:s0").matches()).isFalse();
}
@Test
public void testSelinuxAuditLogsBuilder_noOptionals() {
mAuditLogBuilder.reset(
- "granted { p } scontext=u:r:sdk_sandbox_audit:s0 tcontext=u:object_r:t:s0"
+ "granted { p } scontext=u:r:"
+ + TEST_DOMAIN
+ + ":s0 tcontext=u:object_r:t:s0"
+ " tclass=c");
- assertAuditLog(
- mAuditLogBuilder.build(), true, new String[] {"p"}, "sdk_sandbox_audit", "t", "c");
+ assertAuditLog(mAuditLogBuilder.build(), true, new String[] {"p"}, TEST_DOMAIN, "t", "c");
mAuditLogBuilder.reset(
"tclass=c2 granted { p2 } tcontext=u:object_r:t2:s0"
- + " scontext=u:r:sdk_sandbox_audit:s0");
+ + " scontext=u:r:"
+ + TEST_DOMAIN
+ + ":s0");
assertAuditLog(
- mAuditLogBuilder.build(),
- true,
- new String[] {"p2"},
- "sdk_sandbox_audit",
- "t2",
- "c2");
+ mAuditLogBuilder.build(), true, new String[] {"p2"}, TEST_DOMAIN, "t2", "c2");
}
@Test
public void testSelinuxAuditLogsBuilder_withCategories() {
mAuditLogBuilder.reset(
- "granted { p } scontext=u:r:sdk_sandbox_audit:s0:c123"
+ "granted { p } scontext=u:r:"
+ + TEST_DOMAIN
+ + ":s0:c123"
+ " tcontext=u:object_r:t:s0:c456,c666 tclass=c");
assertAuditLog(
mAuditLogBuilder.build(),
true,
new String[] {"p"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
new int[] {123},
"t",
new int[] {456, 666},
@@ -118,13 +164,15 @@ public class SelinuxAuditLogsBuilderTest {
@Test
public void testSelinuxAuditLogsBuilder_withPath() {
mAuditLogBuilder.reset(
- "granted { p } scontext=u:r:sdk_sandbox_audit:s0 path=\"/very/long/path\""
+ "granted { p } scontext=u:r:"
+ + TEST_DOMAIN
+ + ":s0 path=\"/very/long/path\""
+ " tcontext=u:object_r:t:s0 tclass=c");
assertAuditLog(
mAuditLogBuilder.build(),
true,
new String[] {"p"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"t",
null,
@@ -136,13 +184,15 @@ public class SelinuxAuditLogsBuilderTest {
@Test
public void testSelinuxAuditLogsBuilder_withPermissive() {
mAuditLogBuilder.reset(
- "granted { p } scontext=u:r:sdk_sandbox_audit:s0 permissive=0"
+ "granted { p } scontext=u:r:"
+ + TEST_DOMAIN
+ + ":s0 permissive=0"
+ " tcontext=u:object_r:t:s0 tclass=c");
assertAuditLog(
mAuditLogBuilder.build(),
true,
new String[] {"p"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"t",
null,
@@ -151,13 +201,15 @@ public class SelinuxAuditLogsBuilderTest {
false);
mAuditLogBuilder.reset(
- "granted { p } scontext=u:r:sdk_sandbox_audit:s0 tcontext=u:object_r:t:s0 tclass=c"
+ "granted { p } scontext=u:r:"
+ + TEST_DOMAIN
+ + ":s0 tcontext=u:object_r:t:s0 tclass=c"
+ " permissive=1");
assertAuditLog(
mAuditLogBuilder.build(),
true,
new String[] {"p"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"t",
null,
@@ -166,6 +218,40 @@ public class SelinuxAuditLogsBuilderTest {
true);
}
+ @Test
+ public void testSelinuxAuditLogsBuilder_wrongConfig() {
+ String notARegexDomain = "not]a[regex";
+ runWithShellPermissionIdentity(
+ () ->
+ DeviceConfig.setLocalOverride(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ SelinuxAuditLogBuilder.CONFIG_SELINUX_AUDIT_DOMAIN,
+ notARegexDomain));
+ SelinuxAuditLogBuilder noOpBuilder = new SelinuxAuditLogBuilder();
+
+ noOpBuilder.reset(
+ "granted { p } scontext=u:r:"
+ + TEST_DOMAIN
+ + ":s0 tcontext=u:object_r:t:s0 tclass=c");
+ assertThat(noOpBuilder.build()).isNull();
+ noOpBuilder.reset(
+ "granted { p } scontext=u:r:"
+ + TEST_DOMAIN
+ + ":s0:c123 tcontext=u:object_r:t:s0:c456,c666 tclass=c");
+ assertThat(noOpBuilder.build()).isNull();
+ noOpBuilder.reset(
+ "granted { p } scontext=u:r:"
+ + TEST_DOMAIN
+ + ":s0 path=\"/very/long/path\""
+ + " tcontext=u:object_r:t:s0 tclass=c");
+ assertThat(noOpBuilder.build()).isNull();
+ noOpBuilder.reset(
+ "granted { p } scontext=u:r:"
+ + TEST_DOMAIN
+ + ":s0 permissive=0 tcontext=u:object_r:t:s0 tclass=c");
+ assertThat(noOpBuilder.build()).isNull();
+ }
+
private void assertAuditLog(
SelinuxAuditLog auditLog,
boolean granted,
diff --git a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java
index 4a70ad38a76f..b6ccf5e0ad80 100644
--- a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java
+++ b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java
@@ -15,6 +15,7 @@
*/
package com.android.server.selinux;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -27,6 +28,7 @@ import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
+import android.provider.DeviceConfig;
import android.util.EventLog;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -50,6 +52,7 @@ public class SelinuxAuditLogsCollectorTest {
// Fake tag to use for testing
private static final int ANSWER_TAG = 42;
+ private static final String TEST_DOMAIN = "test_domain";
private final MockClock mClock = new MockClock();
@@ -64,6 +67,14 @@ public class SelinuxAuditLogsCollectorTest {
@Before
public void setUp() {
+ runWithShellPermissionIdentity(
+ () ->
+ DeviceConfig.setLocalOverride(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ SelinuxAuditLogBuilder.CONFIG_SELINUX_AUDIT_DOMAIN,
+ TEST_DOMAIN));
+
+ mSelinuxAutidLogsCollector.setStopRequested(false);
// move the clock forward for the limiters.
mClock.currentTimeMillis += Duration.ofHours(1).toMillis();
// Ignore what was written in the event logs by previous tests.
@@ -74,13 +85,14 @@ public class SelinuxAuditLogsCollectorTest {
@After
public void tearDown() {
+ runWithShellPermissionIdentity(() -> DeviceConfig.clearAllLocalOverrides());
mMockitoSession.finishMocking();
}
@Test
- public void testWriteSdkSandboxAuditLogs() {
- writeTestLog("granted", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm1", "sdk_sandbox_audit", "ttype1", "tclass1");
+ public void testWriteAuditLogs() {
+ writeTestLog("granted", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm1", TEST_DOMAIN, "ttype1", "tclass1");
boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
@@ -91,7 +103,7 @@ public class SelinuxAuditLogsCollectorTest {
FrameworkStatsLog.SELINUX_AUDIT_LOG,
true,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -104,7 +116,7 @@ public class SelinuxAuditLogsCollectorTest {
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm1"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype1",
null,
@@ -114,9 +126,9 @@ public class SelinuxAuditLogsCollectorTest {
}
@Test
- public void testWriteSdkSandboxAuditLogs_multiplePerms() {
- writeTestLog("denied", "perm1 perm2", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm3 perm4", "sdk_sandbox_audit", "ttype", "tclass");
+ public void testWriteAuditLogs_multiplePerms() {
+ writeTestLog("denied", "perm1 perm2", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm3 perm4", TEST_DOMAIN, "ttype", "tclass");
boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
@@ -127,7 +139,7 @@ public class SelinuxAuditLogsCollectorTest {
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm1", "perm2"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -140,7 +152,7 @@ public class SelinuxAuditLogsCollectorTest {
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm3", "perm4"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -150,11 +162,11 @@ public class SelinuxAuditLogsCollectorTest {
}
@Test
- public void testWriteSdkSandboxAuditLogs_withPaths() {
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "/good/path");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "/very/long/path");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "/short_path");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "not_a_path");
+ public void testWriteAuditLogs_withPaths() {
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", "/good/path");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", "/very/long/path");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", "/short_path");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", "not_a_path");
boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
@@ -165,7 +177,7 @@ public class SelinuxAuditLogsCollectorTest {
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -178,7 +190,7 @@ public class SelinuxAuditLogsCollectorTest {
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -191,7 +203,7 @@ public class SelinuxAuditLogsCollectorTest {
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -204,7 +216,7 @@ public class SelinuxAuditLogsCollectorTest {
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -214,23 +226,14 @@ public class SelinuxAuditLogsCollectorTest {
}
@Test
- public void testWriteSdkSandboxAuditLogs_withCategories() {
- writeTestLog(
- "denied", "perm", "sdk_sandbox_audit", new int[] {123}, "ttype", null, "tclass");
- writeTestLog(
- "denied",
- "perm",
- "sdk_sandbox_audit",
- new int[] {123, 456},
- "ttype",
- null,
- "tclass");
- writeTestLog(
- "denied", "perm", "sdk_sandbox_audit", null, "ttype", new int[] {666}, "tclass");
+ public void testWriteAuditLogs_withCategories() {
+ writeTestLog("denied", "perm", TEST_DOMAIN, new int[] {123}, "ttype", null, "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, new int[] {123, 456}, "ttype", null, "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, null, "ttype", new int[] {666}, "tclass");
writeTestLog(
"denied",
"perm",
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
new int[] {123, 456},
"ttype",
new int[] {666, 777},
@@ -245,7 +248,7 @@ public class SelinuxAuditLogsCollectorTest {
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
new int[] {123},
"ttype",
null,
@@ -258,7 +261,7 @@ public class SelinuxAuditLogsCollectorTest {
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
new int[] {123, 456},
"ttype",
null,
@@ -271,7 +274,7 @@ public class SelinuxAuditLogsCollectorTest {
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
new int[] {666},
@@ -284,7 +287,7 @@ public class SelinuxAuditLogsCollectorTest {
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
new int[] {123, 456},
"ttype",
new int[] {666, 777},
@@ -294,11 +297,11 @@ public class SelinuxAuditLogsCollectorTest {
}
@Test
- public void testWriteSdkSandboxAuditLogs_withPathAndCategories() {
+ public void testWriteAuditLogs_withPathAndCategories() {
writeTestLog(
"denied",
"perm",
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
new int[] {123},
"ttype",
new int[] {666},
@@ -314,7 +317,7 @@ public class SelinuxAuditLogsCollectorTest {
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
new int[] {123},
"ttype",
new int[] {666},
@@ -324,10 +327,10 @@ public class SelinuxAuditLogsCollectorTest {
}
@Test
- public void testWriteSdkSandboxAuditLogs_permissive() {
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", true);
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", false);
+ public void testWriteAuditLogs_permissive() {
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", true);
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", false);
boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
@@ -338,7 +341,7 @@ public class SelinuxAuditLogsCollectorTest {
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -352,7 +355,7 @@ public class SelinuxAuditLogsCollectorTest {
FrameworkStatsLog.SELINUX_AUDIT_LOG,
false,
new String[] {"perm"},
- "sdk_sandbox_audit",
+ TEST_DOMAIN,
null,
"ttype",
null,
@@ -362,7 +365,7 @@ public class SelinuxAuditLogsCollectorTest {
}
@Test
- public void testNotWriteAuditLogs_notSdkSandbox() {
+ public void testNotWriteAuditLogs_notTestDomain() {
writeTestLog("denied", "perm", "stype", "ttype", "tclass");
boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
@@ -385,15 +388,15 @@ public class SelinuxAuditLogsCollectorTest {
}
@Test
- public void testWriteSdkSandboxAuditLogs_upToQuota() {
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ public void testWriteAuditLogs_upToQuota() {
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
// These are not pushed.
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
@@ -415,14 +418,14 @@ public class SelinuxAuditLogsCollectorTest {
}
@Test
- public void testWriteSdkSandboxAuditLogs_resetQuota() {
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ public void testWriteAuditLogs_resetQuota() {
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
assertThat(done).isTrue();
@@ -441,11 +444,11 @@ public class SelinuxAuditLogsCollectorTest {
anyBoolean()),
times(5));
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
// move the clock forward to reset the quota limiter.
mClock.currentTimeMillis += Duration.ofHours(1).toMillis();
done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
@@ -468,16 +471,16 @@ public class SelinuxAuditLogsCollectorTest {
@Test
public void testNotWriteAuditLogs_stopRequested() {
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
// These are not pushed.
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
- mSelinuxAutidLogsCollector.mStopRequested.set(true);
+ mSelinuxAutidLogsCollector.setStopRequested(true);
boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
assertThat(done).isFalse();
verify(
@@ -495,7 +498,7 @@ public class SelinuxAuditLogsCollectorTest {
anyBoolean()),
never());
- mSelinuxAutidLogsCollector.mStopRequested.set(false);
+ mSelinuxAutidLogsCollector.setStopRequested(false);
done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
assertThat(done).isTrue();
verify(
@@ -516,8 +519,8 @@ public class SelinuxAuditLogsCollectorTest {
@Test
public void testAuditLogs_resumeJobDoesNotExceedLimit() {
- writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
- mSelinuxAutidLogsCollector.mStopRequested.set(true);
+ writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
+ mSelinuxAutidLogsCollector.setStopRequested(true);
boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
diff --git a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsJobTest.java b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsJobTest.java
new file mode 100644
index 000000000000..2aea8a033f87
--- /dev/null
+++ b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsJobTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.selinux;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+@RunWith(AndroidJUnit4.class)
+public class SelinuxAuditLogsJobTest {
+
+ private final JobService mJobService = mock(JobService.class);
+ private final SelinuxAuditLogsCollector mAuditLogsCollector =
+ mock(SelinuxAuditLogsCollector.class);
+ private final JobParameters mParams = createJobParameters(666);
+ private final SelinuxAuditLogsJob mAuditLogsJob = new SelinuxAuditLogsJob(mAuditLogsCollector);
+
+ @Before
+ public void setUp() {
+ mAuditLogsCollector.mStopRequested = new AtomicBoolean();
+ }
+
+ @Test
+ public void testFinishSuccessfully() {
+ when(mAuditLogsCollector.collect(anyInt())).thenReturn(true);
+
+ mAuditLogsJob.start(mJobService, mParams);
+
+ verify(mJobService).jobFinished(mParams, /* wantsReschedule= */ false);
+ assertThat(mAuditLogsJob.isRunning()).isFalse();
+ }
+
+ @Test
+ public void testInterrupt() {
+ when(mAuditLogsCollector.collect(anyInt())).thenReturn(false);
+
+ mAuditLogsJob.start(mJobService, mParams);
+
+ verify(mJobService, never()).jobFinished(any(), anyBoolean());
+ assertThat(mAuditLogsJob.isRunning()).isFalse();
+ }
+
+ @Test
+ public void testInterruptAndResume() {
+ when(mAuditLogsCollector.collect(anyInt())).thenReturn(false);
+ mAuditLogsJob.start(mJobService, mParams);
+ verify(mJobService, never()).jobFinished(any(), anyBoolean());
+
+ when(mAuditLogsCollector.collect(anyInt())).thenReturn(true);
+ mAuditLogsJob.start(mJobService, mParams);
+ verify(mJobService).jobFinished(mParams, /* wantsReschedule= */ false);
+ assertThat(mAuditLogsJob.isRunning()).isFalse();
+ }
+
+ @Test
+ public void testRequestStop() throws InterruptedException {
+ Semaphore isRunning = new Semaphore(0);
+ Semaphore stopRequested = new Semaphore(0);
+ AtomicReference<Throwable> uncaughtException = new AtomicReference<>();
+
+ // Set up a logs collector that runs in a worker thread until a stop is requested.
+ when(mAuditLogsCollector.collect(anyInt()))
+ .thenAnswer(
+ invocation -> {
+ assertThat(mAuditLogsCollector.mStopRequested.get()).isFalse();
+ isRunning.release();
+ stopRequested.acquire();
+ assertThat(mAuditLogsCollector.mStopRequested.get()).isTrue();
+ return true;
+ });
+ Thread jobThread =
+ new Thread(
+ () -> {
+ mAuditLogsJob.start(mJobService, mParams);
+ });
+ jobThread.setUncaughtExceptionHandler(
+ (thread, exception) -> uncaughtException.set(exception));
+ assertThat(mAuditLogsJob.isRunning()).isFalse();
+ jobThread.start();
+
+ // Wait until the worker thread is running.
+ isRunning.acquire();
+ assertThat(mAuditLogsJob.isRunning()).isTrue();
+
+ // Request for the worker thread to stop, and wait to verify.
+ mAuditLogsJob.requestStop();
+ stopRequested.release();
+ jobThread.join();
+ assertThat(uncaughtException.get()).isNull();
+ assertThat(mAuditLogsJob.isRunning()).isFalse();
+ }
+
+ private static JobParameters createJobParameters(int jobId) {
+ JobParameters jobParameters = mock(JobParameters.class);
+ when(jobParameters.getJobId()).thenReturn(jobId);
+ return jobParameters;
+ }
+}
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 5a1785175be7..6cc650febdc8 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -26,6 +26,7 @@ import static android.view.accessibility.Flags.FLAG_SKIP_ACCESSIBILITY_WARNING_D
import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
import static com.android.server.accessibility.AccessibilityManagerService.ACTION_LAUNCH_HEARING_DEVICES_DIALOG;
+import static com.android.window.flags.Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER;
import static com.google.common.truth.Truth.assertThat;
@@ -68,6 +69,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.LocaleList;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
@@ -110,6 +112,7 @@ import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
import org.junit.After;
+import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -222,6 +225,8 @@ public class AccessibilityManagerServiceTest {
mMockMagnificationConnectionManager);
when(mMockMagnificationController.getFullScreenMagnificationController()).thenReturn(
mMockFullScreenMagnificationController);
+ when(mMockMagnificationController.isFullScreenMagnificationControllerInitialized())
+ .thenReturn(true);
when(mMockMagnificationController.supportWindowMagnification()).thenReturn(true);
when(mMockWindowManagerService.getAccessibilityController()).thenReturn(
mMockA11yController);
@@ -568,6 +573,16 @@ public class AccessibilityManagerServiceTest {
verify(mMockMagnificationController).setAlwaysOnMagnificationEnabled(eq(true));
}
+ @Test
+ @EnableFlags(FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER)
+ public void testSetConnectionNull_borderFlagEnabled_unregisterFullScreenMagnification()
+ throws RemoteException {
+ mA11yms.setMagnificationConnection(null);
+
+ verify(mMockFullScreenMagnificationController, atLeastOnce()).reset(
+ /* displayId= */ anyInt(), /* animate= */ anyBoolean());
+ }
+
@SmallTest
@Test
public void testOnClientChange_magnificationEnabledAndCapabilityAll_requestConnection() {
@@ -989,6 +1004,9 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_enableSoftwareShortcut_shortcutTurnedOn()
throws Exception {
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
mockManageAccessibilityGranted(mTestableContext);
setupShortcutTargetServices();
String target = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
@@ -1008,6 +1026,9 @@ public class AccessibilityManagerServiceTest {
@Test
@EnableFlags(Flags.FLAG_ENABLE_HARDWARE_SHORTCUT_DISABLES_WARNING)
public void enableHardwareShortcutsForTargets_shortcutDialogSetting_isShown() {
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
Settings.Secure.putInt(
mTestableContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
@@ -1035,6 +1056,9 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_disableSoftwareShortcut_shortcutTurnedOff()
throws Exception {
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
String target = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
enableShortcutsForTargets_enableSoftwareShortcut_shortcutTurnedOn();
@@ -1052,6 +1076,9 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_enableSoftwareShortcutWithMagnification_menuSizeIncreased() {
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
mockManageAccessibilityGranted(mTestableContext);
mA11yms.enableShortcutsForTargets(
@@ -1095,6 +1122,9 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_enableAlwaysOnServiceSoftwareShortcut_turnsOnAlwaysOnService()
throws Exception {
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
mockManageAccessibilityGranted(mTestableContext);
setupShortcutTargetServices();
@@ -1115,6 +1145,9 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_disableAlwaysOnServiceSoftwareShortcut_turnsOffAlwaysOnService()
throws Exception {
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
enableShortcutsForTargets_enableAlwaysOnServiceSoftwareShortcut_turnsOnAlwaysOnService();
mA11yms.enableShortcutsForTargets(
@@ -1154,6 +1187,9 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_disableStandardServiceSoftwareShortcutWithServiceOn_wontTurnOffService()
throws Exception {
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
enableShortcutsForTargets_enableStandardServiceSoftwareShortcut_wontTurnOnService();
AccessibilityUtils.setAccessibilityServiceState(
mTestableContext, TARGET_STANDARD_A11Y_SERVICE, /* enabled= */ true);
@@ -1174,6 +1210,9 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_enableTripleTapShortcut_settingUpdated() {
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
mockManageAccessibilityGranted(mTestableContext);
mA11yms.enableShortcutsForTargets(
@@ -1193,6 +1232,9 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_disableTripleTapShortcut_settingUpdated() {
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
enableShortcutsForTargets_enableTripleTapShortcut_settingUpdated();
mA11yms.enableShortcutsForTargets(
@@ -1211,6 +1253,9 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_enableMultiFingerMultiTapsShortcut_settingUpdated() {
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
mockManageAccessibilityGranted(mTestableContext);
mA11yms.enableShortcutsForTargets(
@@ -1230,6 +1275,9 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_disableMultiFingerMultiTapsShortcut_settingUpdated() {
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
enableShortcutsForTargets_enableMultiFingerMultiTapsShortcut_settingUpdated();
mA11yms.enableShortcutsForTargets(
@@ -1249,6 +1297,9 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_enableVolumeKeysShortcut_shortcutSet() {
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
mockManageAccessibilityGranted(mTestableContext);
setupShortcutTargetServices();
@@ -1268,6 +1319,9 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_disableVolumeKeysShortcut_shortcutNotSet() {
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
enableShortcutsForTargets_enableVolumeKeysShortcut_shortcutSet();
mA11yms.enableShortcutsForTargets(
@@ -1286,6 +1340,9 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_enableQuickSettings_shortcutSet() {
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
mockManageAccessibilityGranted(mTestableContext);
setupShortcutTargetServices();
@@ -1311,6 +1368,9 @@ public class AccessibilityManagerServiceTest {
@Test
public void enableShortcutsForTargets_disableQuickSettings_shortcutNotSet() {
+ // TODO(b/111889696): Remove the user 0 assumption once we support multi-user
+ Assume.assumeTrue("The test is setup to run as a user 0",
+ isSameCurrentUser(mA11yms, mTestableContext));
enableShortcutsForTargets_enableQuickSettings_shortcutSet();
mA11yms.enableShortcutsForTargets(
@@ -1695,4 +1755,8 @@ public class AccessibilityManagerServiceTest {
return mBroadcastReceivers;
}
}
+
+ private static boolean isSameCurrentUser(AccessibilityManagerService service, Context context) {
+ return service.getCurrentUserIdLocked() == context.getUserId();
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index f3cd0d6b961e..7b71f85a42b0 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -20,6 +20,7 @@ import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MOD
import static com.android.server.accessibility.magnification.FullScreenMagnificationController.MagnificationInfoChangedCallback;
import static com.android.server.accessibility.magnification.MockMagnificationConnection.TEST_DISPLAY;
+import static com.android.window.flags.Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -139,6 +140,8 @@ public class FullScreenMagnificationControllerTest {
private final TimeAnimator mMockTimeAnimator = mock(TimeAnimator.class);
+ private boolean mMockMagnificationConnectionState;
+
FullScreenMagnificationController mFullScreenMagnificationController;
public DisplayManagerInternal mDisplayManagerInternalMock = mock(DisplayManagerInternal.class);
@@ -175,6 +178,8 @@ public class FullScreenMagnificationControllerTest {
mScaleProvider = new MagnificationScaleProvider(mMockContext);
+ // Assume the connection is established by default
+ mMockMagnificationConnectionState = true;
mFullScreenMagnificationController =
new FullScreenMagnificationController(
mMockControllerCtx,
@@ -184,7 +189,8 @@ public class FullScreenMagnificationControllerTest {
() -> mMockThumbnail,
ConcurrentUtils.DIRECT_EXECUTOR,
() -> mMockScroller,
- () -> mMockTimeAnimator);
+ () -> mMockTimeAnimator,
+ () -> mMockMagnificationConnectionState);
}
@After
@@ -196,7 +202,6 @@ public class FullScreenMagnificationControllerTest {
CURRENT_USER_ID);
}
-
@Test
public void testRegister_WindowManagerAndContextRegisterListeners() {
register(DISPLAY_0);
@@ -291,6 +296,21 @@ public class FullScreenMagnificationControllerTest {
}
@Test
+ @RequiresFlagsEnabled(FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER)
+ public void testSetScale_noConnection_doNothing() {
+ register(TEST_DISPLAY);
+
+ // Assume that the connection does not exist.
+ mMockMagnificationConnectionState = false;
+
+ final float scale = 2.0f;
+ final PointF center = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
+ assertFalse(mFullScreenMagnificationController
+ .setScale(TEST_DISPLAY, scale, center.x, center.y, false, SERVICE_ID_1));
+ assertFalse(mFullScreenMagnificationController.isActivated(TEST_DISPLAY));
+ }
+
+ @Test
public void testSetScale_noAnimation_shouldGoStraightToWindowManagerAndUpdateState() {
for (int i = 0; i < DISPLAY_COUNT; i++) {
setScale_noAnimation_shouldGoStraightToWindowManagerAndUpdateState(i);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 7fbd521b5432..f482ddc7e03c 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -197,6 +197,8 @@ public class FullScreenMagnificationGestureHandlerTest {
private final Scroller mMockScroller = spy(new Scroller(mContext));
+ private boolean mMockMagnificationConnectionState;
+
private OffsettableClock mClock;
private FullScreenMagnificationGestureHandler mMgh;
private TestHandler mHandler;
@@ -229,6 +231,7 @@ public class FullScreenMagnificationGestureHandlerTest {
Settings.Secure.putFloatForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 2.0f,
UserHandle.USER_SYSTEM);
+ mMockMagnificationConnectionState = true;
mFullScreenMagnificationController =
new FullScreenMagnificationController(
mockController,
@@ -238,7 +241,8 @@ public class FullScreenMagnificationGestureHandlerTest {
() -> null,
ConcurrentUtils.DIRECT_EXECUTOR,
() -> mMockScroller,
- TimeAnimator::new) {
+ TimeAnimator::new,
+ () -> mMockMagnificationConnectionState) {
@Override
public boolean magnificationRegionContains(int displayId, float x, float y) {
return true;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index 58567ca2380f..25281774cd95 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -220,7 +220,8 @@ public class MagnificationControllerTest {
() -> null,
ConcurrentUtils.DIRECT_EXECUTOR,
() -> mMockScroller,
- () -> mTimeAnimator));
+ () -> mTimeAnimator,
+ () -> true));
mScreenMagnificationController.register(TEST_DISPLAY);
mMagnificationConnectionManager = spy(
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 7c0dbf4889da..c6f3eb357442 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -34,6 +34,7 @@ import static com.android.server.am.UserController.CONTINUE_USER_SWITCH_MSG;
import static com.android.server.am.UserController.REPORT_LOCKED_BOOT_COMPLETE_MSG;
import static com.android.server.am.UserController.REPORT_USER_SWITCH_COMPLETE_MSG;
import static com.android.server.am.UserController.REPORT_USER_SWITCH_MSG;
+import static com.android.server.am.UserController.SCHEDULED_STOP_BACKGROUND_USER_MSG;
import static com.android.server.am.UserController.USER_COMPLETED_EVENT_MSG;
import static com.android.server.am.UserController.USER_CURRENT_MSG;
import static com.android.server.am.UserController.USER_START_MSG;
@@ -323,7 +324,8 @@ public class UserControllerTest {
@Test
public void testStartUserUIDisabled() {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
verify(mInjector, never()).showUserSwitchingDialog(
@@ -393,7 +395,8 @@ public class UserControllerTest {
@Test
public void testFailedStartUserInForeground() {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
mUserController.startUserInForeground(NONEXIST_USER_ID);
verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean());
@@ -470,7 +473,8 @@ public class UserControllerTest {
@Test
public void testContinueUserSwitch() {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
// Start user -- this will update state of mUserController
mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
@@ -483,7 +487,7 @@ public class UserControllerTest {
continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
verify(mInjector, times(0)).dismissKeyguard(any());
verify(mInjector, times(1)).dismissUserSwitchingDialog(any());
- continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false);
+ continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false, false);
verifySystemUserVisibilityChangesNeverNotified();
}
@@ -491,7 +495,8 @@ public class UserControllerTest {
public void testContinueUserSwitchDismissKeyguard() {
when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(false);
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
// Start user -- this will update state of mUserController
mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
@@ -504,14 +509,15 @@ public class UserControllerTest {
continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
verify(mInjector, times(1)).dismissKeyguard(any());
verify(mInjector, times(1)).dismissUserSwitchingDialog(any());
- continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false);
+ continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false, false);
verifySystemUserVisibilityChangesNeverNotified();
}
@Test
public void testContinueUserSwitchUIDisabled() {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
// Start user -- this will update state of mUserController
mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
@@ -524,11 +530,11 @@ public class UserControllerTest {
// Verify that continueUserSwitch worked as expected
continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
verify(mInjector, never()).dismissUserSwitchingDialog(any());
- continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false);
+ continueUserSwitchAssertions(oldUserId, TEST_USER_ID, false, false);
}
private void continueUserSwitchAssertions(int expectedOldUserId, int expectedNewUserId,
- boolean backgroundUserStopping) {
+ boolean backgroundUserStopping, boolean expectScheduleBackgroundUserStopping) {
Set<Integer> expectedCodes = new LinkedHashSet<>();
expectedCodes.add(COMPLETE_USER_SWITCH_MSG);
expectedCodes.add(REPORT_USER_SWITCH_COMPLETE_MSG);
@@ -536,6 +542,9 @@ public class UserControllerTest {
expectedCodes.add(CLEAR_USER_JOURNEY_SESSION_MSG);
expectedCodes.add(0); // this is for directly posting in stopping.
}
+ if (expectScheduleBackgroundUserStopping) {
+ expectedCodes.add(SCHEDULED_STOP_BACKGROUND_USER_MSG);
+ }
Set<Integer> actualCodes = mInjector.mHandler.getMessageCodes();
assertEquals("Unexpected message sent", expectedCodes, actualCodes);
Message msg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_COMPLETE_MSG);
@@ -571,6 +580,112 @@ public class UserControllerTest {
).collect(Collectors.toList()), Collections.emptySet());
}
+ /** Test scheduling stopping of background users after a user-switch. */
+ @Test
+ public void testScheduleStopOfBackgroundUser_switch() {
+ mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SCHEDULE_STOP_OF_BACKGROUND_USER);
+
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+ /* maxRunningUsers= */ 10, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ 2);
+
+ setUpUser(TEST_USER_ID1, NO_USERINFO_FLAGS);
+
+ // Switch to TEST_USER_ID from user 0
+ int numberOfUserSwitches = 0;
+ addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM,
+ ++numberOfUserSwitches,
+ /* expectOldUserStopping= */false,
+ /* expectScheduleBackgroundUserStopping= */ false);
+ assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID),
+ mUserController.getRunningUsersLU());
+
+ // Allow the post-switch processing to complete (there should be no scheduled stopping).
+ assertAndProcessScheduledStopBackgroundUser(false, null);
+ assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID),
+ mUserController.getRunningUsersLU());
+
+ // Switch to TEST_USER_ID1 from TEST_USER_ID
+ addForegroundUserAndContinueUserSwitch(TEST_USER_ID1, TEST_USER_ID,
+ ++numberOfUserSwitches,
+ /* expectOldUserStopping= */false,
+ /* expectScheduleBackgroundUserStopping= */ true);
+ assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID, TEST_USER_ID1),
+ mUserController.getRunningUsersLU());
+
+ // Switch back to TEST_USER_ID from TEST_USER_ID1
+ addForegroundUserAndContinueUserSwitch(TEST_USER_ID, TEST_USER_ID1,
+ ++numberOfUserSwitches,
+ /* expectOldUserStopping= */false,
+ /* expectScheduleBackgroundUserStopping= */ true);
+ assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID1, TEST_USER_ID),
+ mUserController.getRunningUsersLU());
+
+ // Allow the post-switch processing to complete.
+ assertAndProcessScheduledStopBackgroundUser(false, TEST_USER_ID);
+ assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID1);
+ assertAndProcessScheduledStopBackgroundUser(false, null);
+ assertEquals(Arrays.asList(SYSTEM_USER_ID, TEST_USER_ID),
+ mUserController.getRunningUsersLU());
+ }
+
+ /** Test scheduling stopping of background users that were started in the background. */
+ @Test
+ public void testScheduleStopOfBackgroundUser_startInBackground() throws Exception {
+ mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SCHEDULE_STOP_OF_BACKGROUND_USER);
+
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+ /* maxRunningUsers= */ 10, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ 2);
+
+ // Start two full background users (which should both get scheduled for stopping)
+ // and one profile (which should not).
+ setUpAndStartUserInBackground(TEST_USER_ID);
+ setUpAndStartUserInBackground(TEST_USER_ID1);
+ setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_MANAGED);
+
+ assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID, TEST_USER_ID1, TEST_USER_ID2),
+ new HashSet<>(mUserController.getRunningUsersLU()));
+
+ assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID);
+ assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID1, TEST_USER_ID2),
+ new HashSet<>(mUserController.getRunningUsersLU()));
+
+ assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID1);
+ assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID2),
+ new HashSet<>(mUserController.getRunningUsersLU()));
+
+ assertAndProcessScheduledStopBackgroundUser(false, TEST_USER_ID2);
+ assertAndProcessScheduledStopBackgroundUser(false, null);
+
+ // Now that we've processed the stops, let's make sure that a subsequent one will work too.
+ setUpAndStartUserInBackground(TEST_USER_ID3);
+ assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID2, TEST_USER_ID3),
+ new HashSet<>(mUserController.getRunningUsersLU()));
+ assertAndProcessScheduledStopBackgroundUser(true, TEST_USER_ID3);
+ assertAndProcessScheduledStopBackgroundUser(false, null);
+ assertEquals(newHashSet(SYSTEM_USER_ID, TEST_USER_ID2),
+ new HashSet<>(mUserController.getRunningUsersLU()));
+ }
+
+ /**
+ * Process queued SCHEDULED_STOP_BACKGROUND_USER_MSG message, if expected.
+ * @param userId the user we are checking to see whether it is scheduled.
+ * Can be null, when expectScheduled is false, to indicate no user should be
+ * scheduled.
+ */
+ private void assertAndProcessScheduledStopBackgroundUser(
+ boolean expectScheduled, @Nullable Integer userId) {
+ TestHandler handler = mInjector.mHandler;
+ if (expectScheduled) {
+ assertTrue(handler.hasMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, userId));
+ handler.removeMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, userId);
+ mUserController.processScheduledStopOfBackgroundUser(userId);
+ } else {
+ assertFalse(handler.hasMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, userId));
+ }
+ }
+
@Test
public void testExplicitSystemUserStartInBackground() {
setUpUser(UserHandle.USER_SYSTEM, 0);
@@ -587,13 +702,14 @@ public class UserControllerTest {
public void testUserLockingFromUserSwitchingForMultipleUsersNonDelayedLocking()
throws InterruptedException, RemoteException {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
setUpUser(TEST_USER_ID1, 0);
setUpUser(TEST_USER_ID2, 0);
int numberOfUserSwitches = 1;
addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM,
- numberOfUserSwitches, false);
+ numberOfUserSwitches, false, false);
// running: user 0, USER_ID
assertTrue(mUserController.canStartMoreUsers());
assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID}),
@@ -601,7 +717,7 @@ public class UserControllerTest {
numberOfUserSwitches++;
addForegroundUserAndContinueUserSwitch(TEST_USER_ID1, TEST_USER_ID,
- numberOfUserSwitches, false);
+ numberOfUserSwitches, false, false);
// running: user 0, USER_ID, USER_ID1
assertFalse(mUserController.canStartMoreUsers());
assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID, TEST_USER_ID1}),
@@ -609,7 +725,7 @@ public class UserControllerTest {
numberOfUserSwitches++;
addForegroundUserAndContinueUserSwitch(TEST_USER_ID2, TEST_USER_ID1,
- numberOfUserSwitches, false);
+ numberOfUserSwitches, false, false);
UserState ussUser2 = mUserStates.get(TEST_USER_ID2);
// skip middle step and call this directly.
mUserController.finishUserSwitch(ussUser2);
@@ -631,13 +747,14 @@ public class UserControllerTest {
public void testUserLockingFromUserSwitchingForMultipleUsersDelayedLockingMode()
throws Exception {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
setUpUser(TEST_USER_ID1, 0);
setUpUser(TEST_USER_ID2, 0);
int numberOfUserSwitches = 1;
addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM,
- numberOfUserSwitches, false);
+ numberOfUserSwitches, false, false);
// running: user 0, USER_ID
assertTrue(mUserController.canStartMoreUsers());
assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID}),
@@ -645,7 +762,7 @@ public class UserControllerTest {
numberOfUserSwitches++;
addForegroundUserAndContinueUserSwitch(TEST_USER_ID1, TEST_USER_ID,
- numberOfUserSwitches, true);
+ numberOfUserSwitches, true, false);
// running: user 0, USER_ID1
// stopped + unlocked: USER_ID
numberOfUserSwitches++;
@@ -663,7 +780,7 @@ public class UserControllerTest {
.lockCeStorage(anyInt());
addForegroundUserAndContinueUserSwitch(TEST_USER_ID2, TEST_USER_ID1,
- numberOfUserSwitches, true);
+ numberOfUserSwitches, true, false);
// running: user 0, USER_ID2
// stopped + unlocked: USER_ID1
// stopped + locked: USER_ID
@@ -686,7 +803,8 @@ public class UserControllerTest {
public void testStoppingExcessRunningUsersAfterSwitch_currentProfileNotStopped()
throws Exception {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 5, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 5, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
final int PARENT_ID = 200;
final int PROFILE1_ID = 201;
@@ -707,7 +825,7 @@ public class UserControllerTest {
int numberOfUserSwitches = 1;
addForegroundUserAndContinueUserSwitch(PARENT_ID, UserHandle.USER_SYSTEM,
- numberOfUserSwitches, false);
+ numberOfUserSwitches, false, false);
mUserController.finishUserSwitch(mUserStates.get(PARENT_ID));
waitForHandlerToComplete(mInjector.mHandler, HANDLER_WAIT_TIME_MS);
assertTrue(mUserController.canStartMoreUsers());
@@ -722,7 +840,7 @@ public class UserControllerTest {
numberOfUserSwitches++;
addForegroundUserAndContinueUserSwitch(FG_USER_ID, PARENT_ID,
- numberOfUserSwitches, false);
+ numberOfUserSwitches, false, false);
mUserController.finishUserSwitch(mUserStates.get(FG_USER_ID));
waitForHandlerToComplete(mInjector.mHandler, HANDLER_WAIT_TIME_MS);
assertTrue(mUserController.canStartMoreUsers());
@@ -747,7 +865,7 @@ public class UserControllerTest {
numberOfUserSwitches++;
addForegroundUserAndContinueUserSwitch(PARENT_ID, FG_USER_ID,
- numberOfUserSwitches, false);
+ numberOfUserSwitches, false, false);
mUserController.finishUserSwitch(mUserStates.get(PARENT_ID));
waitForHandlerToComplete(mInjector.mHandler, HANDLER_WAIT_TIME_MS);
// We've now done a user switch and should notice that we've exceeded the maximum number of
@@ -766,7 +884,8 @@ public class UserControllerTest {
@Test
public void testRunningUsersListOrder_parentAfterProfile() {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 7, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 7, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
final int PARENT_ID = 200;
final int PROFILE1_ID = 201;
@@ -787,7 +906,7 @@ public class UserControllerTest {
int numberOfUserSwitches = 1;
addForegroundUserAndContinueUserSwitch(PARENT_ID, UserHandle.USER_SYSTEM,
- numberOfUserSwitches, false);
+ numberOfUserSwitches, false, false);
assertEquals(Arrays.asList(
new Integer[] {SYSTEM_USER_ID, PARENT_ID}),
mUserController.getRunningUsersLU());
@@ -799,7 +918,7 @@ public class UserControllerTest {
numberOfUserSwitches++;
addForegroundUserAndContinueUserSwitch(FG_USER_ID, PARENT_ID,
- numberOfUserSwitches, false);
+ numberOfUserSwitches, false, false);
assertEquals(Arrays.asList(
new Integer[] {SYSTEM_USER_ID, PROFILE1_ID, PARENT_ID, FG_USER_ID}),
mUserController.getRunningUsersLU());
@@ -827,7 +946,8 @@ public class UserControllerTest {
@Test
public void testRunningUsersListOrder_currentAtEnd() {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 7, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 7, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
final int CURRENT_ID = 200;
final int PROFILE_ID = 201;
@@ -842,7 +962,7 @@ public class UserControllerTest {
new Integer[] {SYSTEM_USER_ID}),
mUserController.getRunningUsersLU());
- addForegroundUserAndContinueUserSwitch(CURRENT_ID, UserHandle.USER_SYSTEM, 1, false);
+ addForegroundUserAndContinueUserSwitch(CURRENT_ID, UserHandle.USER_SYSTEM, 1, false, false);
assertEquals(Arrays.asList(
new Integer[] {SYSTEM_USER_ID, CURRENT_ID}),
mUserController.getRunningUsersLU());
@@ -864,7 +984,8 @@ public class UserControllerTest {
@Test
public void testUserLockingWithStopUserForNonDelayedLockingMode() throws Exception {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
setUpAndStartUserInBackground(TEST_USER_ID);
assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* allowDelayedLocking= */ true,
@@ -922,7 +1043,8 @@ public class UserControllerTest {
@Test
public void testUserLockingForDelayedLockingMode() throws Exception {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
// allowDelayedLocking set and no KeyEvictedCallback, so it should not lock.
setUpAndStartUserInBackground(TEST_USER_ID);
@@ -973,7 +1095,8 @@ public class UserControllerTest {
@Test
public void testStopProfile_doesNotStopItsParent() throws Exception {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 5, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 5, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
final Range<Integer> RUNNING_RANGE =
Range.closed(UserState.STATE_BOOTING, UserState.STATE_RUNNING_UNLOCKED);
@@ -1053,7 +1176,8 @@ public class UserControllerTest {
@Test
public void testStopPrivateProfile() throws Exception {
mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
@@ -1071,7 +1195,8 @@ public class UserControllerTest {
@Test
public void testStopPrivateProfileWithDelayedLocking() throws Exception {
mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
@@ -1083,7 +1208,8 @@ public class UserControllerTest {
@Test
public void testStopPrivateProfileWithDelayedLocking_flagDisabled() throws Exception {
mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
mSetFlagsRule.disableFlags(
@@ -1113,7 +1239,8 @@ public class UserControllerTest {
public void testStopPrivateProfileWithDelayedLocking_imperviousToNumberOfRunningUsers()
throws Exception {
mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
- /* maxRunningUsers= */ 1, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 1, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
@@ -1130,7 +1257,8 @@ public class UserControllerTest {
@Test
public void testStopManagedProfileWithDelayedLocking() throws Exception {
mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
@@ -1285,7 +1413,8 @@ public class UserControllerTest {
public void testStallUserSwitchUntilTheKeyguardIsShown() throws Exception {
// enable user switch ui, because keyguard is only shown then
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
- /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false,
+ /* backgroundUserScheduledStopTimeSecs= */ -1);
// mock the device to be secure in order to expect the keyguard to be shown
when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true);
@@ -1365,7 +1494,8 @@ public class UserControllerTest {
}
private void addForegroundUserAndContinueUserSwitch(int newUserId, int expectedOldUserId,
- int expectedNumberOfCalls, boolean expectOldUserStopping) {
+ int expectedNumberOfCalls, boolean expectOldUserStopping,
+ boolean expectScheduleBackgroundUserStopping) {
// Start user -- this will update state of mUserController
mUserController.startUser(newUserId, USER_START_MODE_FOREGROUND);
Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
@@ -1378,8 +1508,12 @@ public class UserControllerTest {
mInjector.mHandler.clearAllRecordedMessages();
// Verify that continueUserSwitch worked as expected
continueAndCompleteUserSwitch(userState, oldUserId, newUserId);
+ assertEquals(mInjector.mHandler
+ .hasMessages(SCHEDULED_STOP_BACKGROUND_USER_MSG, expectedOldUserId),
+ expectScheduleBackgroundUserStopping);
verify(mInjector, times(expectedNumberOfCalls)).dismissUserSwitchingDialog(any());
- continueUserSwitchAssertions(oldUserId, newUserId, expectOldUserStopping);
+ continueUserSwitchAssertions(oldUserId, newUserId, expectOldUserStopping,
+ expectScheduleBackgroundUserStopping);
}
private UserInfo setUpUser(@UserIdInt int userId, @UserInfoFlag int flags) {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
index 3f5217c371de..8ca862390a65 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
@@ -39,6 +39,7 @@ import static org.mockito.Mockito.when;
import android.content.ComponentName;
import android.content.pm.PackageManagerInternal;
import android.frameworks.vibrator.ScaleParam;
+import android.frameworks.vibrator.VibrationParam;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -59,6 +60,8 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.Arrays;
+import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@@ -177,6 +180,24 @@ public class VibratorControlServiceTest {
verifyZeroInteractions(mMockVibrationScaler);
}
+ @Test(expected = IllegalArgumentException.class)
+ public void testOnRequestVibrationParamsComplete_withNullVibrationParams_throwsException() {
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
+ int timeoutInMillis = 10;
+ CompletableFuture<Void> unusedFuture =
+ mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE,
+ timeoutInMillis);
+ IBinder token = mVibratorControlService.getRequestVibrationParamsToken();
+
+ List<VibrationParam> vibrationParamList = Arrays.asList(
+ VibrationParamGenerator.generateVibrationParam(ScaleParam.TYPE_ALARM, 0.7f),
+ null,
+ VibrationParamGenerator.generateVibrationParam(ScaleParam.TYPE_NOTIFICATION, 0.4f));
+
+ mVibratorControlService.onRequestVibrationParamsComplete(token,
+ vibrationParamList.toArray(new VibrationParam[0]));
+ }
+
@Test
public void testSetVibrationParams_cachesAdaptiveHapticsScalesCorrectly() {
mVibratorControlService.registerVibratorController(mFakeVibratorController);
@@ -214,6 +235,19 @@ public class VibratorControlServiceTest {
verifyZeroInteractions(mMockVibrationScaler);
}
+ @Test(expected = IllegalArgumentException.class)
+ public void testSetVibrationParams_withNullVibrationParams_throwsException() {
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
+ List<VibrationParam> vibrationParamList = Arrays.asList(
+ VibrationParamGenerator.generateVibrationParam(ScaleParam.TYPE_ALARM, 0.7f),
+ null,
+ VibrationParamGenerator.generateVibrationParam(ScaleParam.TYPE_NOTIFICATION, 0.4f));
+
+ mVibratorControlService.setVibrationParams(
+ vibrationParamList.toArray(new VibrationParam[0]),
+ mFakeVibratorController);
+ }
+
@Test
public void testClearVibrationParams_clearsCachedAdaptiveHapticsScales() {
mVibratorControlService.registerVibratorController(mFakeVibratorController);
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/VibrationParamGenerator.java b/services/tests/vibrator/utils/com/android/server/vibrator/VibrationParamGenerator.java
index a606388da190..c17d11e51497 100644
--- a/services/tests/vibrator/utils/com/android/server/vibrator/VibrationParamGenerator.java
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/VibrationParamGenerator.java
@@ -42,7 +42,10 @@ public final class VibrationParamGenerator {
return vibrationParamList.toArray(new VibrationParam[0]);
}
- private static VibrationParam generateVibrationParam(int type, float scale) {
+ /**
+ * Generates a {@link VibrationParam} with the specified type and scale.
+ */
+ public static VibrationParam generateVibrationParam(int type, float scale) {
ScaleParam scaleParam = new ScaleParam();
scaleParam.typesMask = type;
scaleParam.scale = scale;
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 5aabea38bf5b..b41db3170ef6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -643,7 +643,8 @@ public class LetterboxUiControllerTest extends WindowTestsBase {
doReturn(false).when(mActivity).isInLetterboxAnimation();
assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow));
- doReturn(false).when(mainWindow).isOnScreen();
+ doReturn(false).when(mActivity).isVisibleRequested();
+ doReturn(false).when(mActivity).isVisible();
assertEquals(0, mController.getRoundedCornersRadius(mainWindow));
doReturn(true).when(mActivity).isInLetterboxAnimation();
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 2e80bc721c7f..9697c65dc1ea 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -130,7 +130,6 @@ import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
@@ -916,8 +915,7 @@ public class SizeCompatTests extends WindowTestsBase {
assertEquals(window, mActivity.findMainWindow());
spyOn(mActivity.mLetterboxUiController);
- doReturn(true).when(mActivity.mLetterboxUiController)
- .isSurfaceVisible(any());
+ doReturn(true).when(mActivity).isVisibleRequested();
assertTrue(mActivity.mLetterboxUiController.shouldShowLetterboxUi(
mActivity.findMainWindow()));
@@ -1943,8 +1941,7 @@ public class SizeCompatTests extends WindowTestsBase {
assertThat(mActivity.inSizeCompatMode()).isTrue();
assertActivityMaxBoundsSandboxed();
-
- final int scale = dh / dw;
+ final int scale = dh / dw;
// App bounds should be dh / scale x dw / scale
assertEquals(dw, rotatedDisplayBounds.width());
@@ -4179,13 +4176,8 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
- @Ignore // TODO(b/330888878): fix test in main
- public void testPortraitCloseToSquareDisplayWithTaskbar_notLetterboxed() {
- if (Flags.insetsDecoupledConfiguration()) {
- // TODO (b/151861875): Re-enable it. This is disabled temporarily because the config
- // bounds no longer contains display cutout.
- return;
- }
+ @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED})
+ public void testPortraitCloseToSquareDisplayWithTaskbar_letterboxed() {
// Set up portrait close to square display
setUpDisplaySizeWithApp(2200, 2280);
final DisplayContent display = mActivity.mDisplayContent;
@@ -4198,16 +4190,58 @@ public class SizeCompatTests extends WindowTestsBase {
.setInsetsSize(Insets.of(0, 0, 0, 150))
};
display.getDisplayPolicy().addWindowLw(navbar, navbar.mAttrs);
- assertTrue(navbar.providesDisplayDecorInsets()
- && display.getDisplayPolicy().updateDecorInsetsInfo());
+ assertTrue(display.getDisplayPolicy().updateDecorInsetsInfo());
display.sendNewConfiguration();
- prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setTask(mTask)
+ .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+ .setComponent(ComponentName.createRelative(mContext,
+ SizeCompatTests.class.getName()))
+ .setUid(android.os.Process.myUid())
+ .build();
- // Activity is fullscreen even though orientation is not respected with insets, because
- // the display still matches or is less than the activity aspect ratio
- assertEquals(display.getBounds(), mActivity.getBounds());
- assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ final Rect bounds = activity.getBounds();
+ // Activity should be letterboxed and should have portrait app bounds
+ assertTrue(activity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertTrue(bounds.height() > bounds.width());
+ }
+
+ @Test
+ @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED})
+ public void testFixedAspectRatioAppInPortraitCloseToSquareDisplay_notInSizeCompat() {
+ setUpDisplaySizeWithApp(2200, 2280);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ final DisplayContent dc = mActivity.mDisplayContent;
+ // Simulate taskbar, final app bounds are (0, 0, 2200, 2130) - landscape
+ final WindowState navbar = createWindow(null, TYPE_NAVIGATION_BAR, mDisplayContent,
+ "navbar");
+ final Binder owner = new Binder();
+ navbar.mAttrs.providedInsets = new InsetsFrameProvider[] {
+ new InsetsFrameProvider(owner, 0, WindowInsets.Type.navigationBars())
+ .setInsetsSize(Insets.of(0, 0, 0, 150))
+ };
+ dc.getDisplayPolicy().addWindowLw(navbar, navbar.mAttrs);
+ assertTrue(dc.getDisplayPolicy().updateDecorInsetsInfo());
+ dc.sendNewConfiguration();
+
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setTask(mTask)
+ .setComponent(ComponentName.createRelative(mContext,
+ SizeCompatTests.class.getName()))
+ .setUid(android.os.Process.myUid())
+ .build();
+ prepareMinAspectRatio(activity, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
+ SCREEN_ORIENTATION_LANDSCAPE);
+ // To force config to update again but with the same landscape orientation.
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
+
+ assertTrue(activity.shouldCreateCompatDisplayInsets());
+ assertNotNull(activity.getCompatDisplayInsets());
+ // Activity is not letterboxed for fixed orientation because orientation is respected
+ // with insets, and should not be in size compat mode
+ assertFalse(activity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertFalse(activity.inSizeCompatMode());
}
@Test
@@ -4229,6 +4263,7 @@ public class SizeCompatTests extends WindowTestsBase {
// can be aligned inside parentAppBounds
assertEquals(mActivity.getBounds(), new Rect(0, 0, 1000, 2200));
}
+
@Test
public void testApplyAspectRatio_activityCannotAlignWithParentAppVertical() {
if (Flags.insetsDecoupledConfiguration()) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 1ca808f4153a..225e85e03b26 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -833,8 +833,11 @@ public class TaskTests extends WindowTestsBase {
final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build();
final ActivityRecord.CompatDisplayInsets compatInsets =
new ActivityRecord.CompatDisplayInsets(
- display, activity, /* fixedOrientationBounds= */ null);
- task.computeConfigResourceOverrides(inOutConfig, parentConfig, compatInsets);
+ display, activity, /* letterboxedContainerBounds */ null,
+ /* useOverrideInsets */ false);
+ final TaskFragment.ConfigOverrideHint overrideHint = new TaskFragment.ConfigOverrideHint();
+ overrideHint.mTmpCompatInsets = compatInsets;
+ task.computeConfigResourceOverrides(inOutConfig, parentConfig, overrideHint);
assertEquals(largerLandscapeBounds, inOutConfig.windowConfiguration.getAppBounds());
final float density = parentConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 72bedf2da21f..5b1a18da3173 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -410,6 +410,8 @@ public class WallpaperControllerTests extends WindowTestsBase {
final WindowState wallpaperWindow = createWallpaperWindow(dc);
final WallpaperWindowToken token = wallpaperWindow.mToken.asWallpaperToken();
wallpaperWindow.setHasSurface(true);
+ spyOn(dc.mWallpaperController);
+ doReturn(wallpaperWindow).when(dc.mWallpaperController).getWallpaperTarget();
// Set-up mock shell transitions
registerTestTransitionPlayer();
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index ba7ba53272f1..8fe45cbb0430 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -4614,6 +4614,31 @@ public class SubscriptionManager {
}
/**
+ * Set owner for this subscription.
+ *
+ * @param subscriptionId the subId of the subscription.
+ * @param groupOwner The group owner to assign to the subscription
+ *
+ * @throws SecurityException if caller is not authorized.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public void setGroupOwner(int subscriptionId, @NonNull String groupOwner) {
+ try {
+ ISub iSub = TelephonyManager.getSubscriptionService();
+ if (iSub != null) {
+ iSub.setGroupOwner(subscriptionId, groupOwner);
+ } else {
+ throw new IllegalStateException("[setGroupOwner]: "
+ + "subscription service unavailable");
+ }
+ } catch (RemoteException ex) {
+ ex.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
* Set userHandle for a subscription.
*
* Used to set an association between a subscription and a user on the device so that voice
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 6678f408e720..1bfec29a3cf4 100644
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -309,6 +309,18 @@ interface ISub {
*/
int setUsageSetting(int usageSetting, int subId, String callingPackage);
+ /**
+ * Set owner for this subscription.
+ *
+ * @param subId the unique SubscriptionInfo index in database
+ * @param groupOwner The group owner to assign to the subscription
+ *
+ * @throws SecurityException if caller is not authorized.
+ *
+ * @hide
+ */
+ void setGroupOwner(int subId, String groupOwner);
+
/**
* Set userHandle for this subscription.
*
diff --git a/tests/CtsSurfaceControlTestsStaging/Android.bp b/tests/CtsSurfaceControlTestsStaging/Android.bp
index 96e4a9ea4300..1038c9e93931 100644
--- a/tests/CtsSurfaceControlTestsStaging/Android.bp
+++ b/tests/CtsSurfaceControlTestsStaging/Android.bp
@@ -19,6 +19,7 @@ package {
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
+ default_team: "trendy_team_android_core_graphics_stack",
}
android_test {
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 1fdf97a4c821..093923f3ed53 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -45,13 +45,13 @@ import android.os.test.TestLooper;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.util.AtomicFile;
+import android.util.LongArrayQueue;
import android.util.Xml;
-import android.utils.LongArrayQueue;
-import android.utils.XmlUtils;
import androidx.test.InstrumentationRegistry;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.PackageWatchdog.HealthCheckState;
diff --git a/tests/TouchLatency/app/src/main/res/values/styles.xml b/tests/TouchLatency/app/src/main/res/values/styles.xml
index b23a87e57754..fa352cf1e832 100644
--- a/tests/TouchLatency/app/src/main/res/values/styles.xml
+++ b/tests/TouchLatency/app/src/main/res/values/styles.xml
@@ -18,6 +18,7 @@
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
<!-- Customize your theme here. -->
+ <item name="android:windowLayoutInDisplayCutoutMode">default</item>
</style>
</resources>
diff --git a/tests/WindowInsetsTests/AndroidManifest.xml b/tests/WindowInsetsTests/AndroidManifest.xml
index 61dd9d4cd021..dbe9d363e285 100644
--- a/tests/WindowInsetsTests/AndroidManifest.xml
+++ b/tests/WindowInsetsTests/AndroidManifest.xml
@@ -18,7 +18,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.test.windowinsetstests">
- <application android:label="@string/application_title">
+ <application android:label="@string/application_title"
+ android:theme="@style/base">
<activity android:name=".WindowInsetsTestsMainActivity"
android:exported="true">
<intent-filter>
@@ -29,11 +30,9 @@
<activity android:name=".ChatActivity"
android:label="@string/chat_activity_title"
- android:theme="@style/chat"
android:windowSoftInputMode="adjustResize" />
<activity android:name=".ControllerActivity"
- android:label="@string/controller_activity_title"
- android:theme="@style/controller" />
+ android:label="@string/controller_activity_title" />
</application>
</manifest>
diff --git a/tests/WindowInsetsTests/res/layout/controller_activity.xml b/tests/WindowInsetsTests/res/layout/controller_activity.xml
index 5550eab61a33..7013059e1334 100644
--- a/tests/WindowInsetsTests/res/layout/controller_activity.xml
+++ b/tests/WindowInsetsTests/res/layout/controller_activity.xml
@@ -15,92 +15,110 @@
-->
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/root"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <androidx.appcompat.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ />
- <LinearLayout
- android:id="@+id/content"
+ <ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="vertical">
-
- <Spinner
- android:id="@+id/spinnerBehavior"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="10dp"
- android:layout_marginBottom="20dp" />
-
- <ToggleButton
- android:id="@+id/toggleButtonStatus"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:checked="true"
- android:text="Status Bars Toggle Button"
- android:textOff="Status Bars Invisible"
- android:textOn="Status Bars Visible" />
-
- <SeekBar
- android:id="@+id/seekBarStatus"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="10dp"
- android:layout_marginBottom="20dp"
- android:max="10000"
- android:progress="10000" />
-
- <ToggleButton
- android:id="@+id/toggleButtonNavigation"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:checked="true"
- android:text="Navigation Bars Toggle Button"
- android:textOff="Navigation Bars Invisible"
- android:textOn="Navigation Bars Visible" />
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp">
- <SeekBar
- android:id="@+id/seekBarNavigation"
+ <LinearLayout
+ android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="10dp"
- android:layout_marginBottom="20dp"
- android:max="10000"
- android:progress="10000" />
-
- <ToggleButton
- android:id="@+id/toggleButtonIme"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:checked="true"
- android:text="IME Toggle Button"
- android:textOff="IME Invisible"
- android:textOn="IME Visible" />
-
- <SeekBar
- android:id="@+id/seekBarIme"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="10dp"
- android:layout_marginBottom="20dp"
- android:max="10000"
- android:progress="0" />
-
- <TextView
- android:id="@+id/textViewControllableInsets"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_margin="5dp" />
-
- <EditText
- android:id="@+id/editText"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:ems="10"
- android:hint="For testing IME..."
- android:inputType="text"
- android:text="" />
-
- </LinearLayout>
-
-</ScrollView> \ No newline at end of file
+ android:orientation="vertical">
+
+ <Spinner
+ android:id="@+id/spinnerBehavior"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="20dp" />
+
+ <ToggleButton
+ android:id="@+id/toggleButtonStatus"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:checked="true"
+ android:text="@string/status_bars_toggle_button"
+ android:textOff="@string/status_bars_invisible"
+ android:textOn="@string/status_bars_visible" />
+
+ <SeekBar
+ android:id="@+id/seekBarStatus"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="20dp"
+ android:max="10000"
+ android:progress="10000" />
+
+ <ToggleButton
+ android:id="@+id/toggleButtonNavigation"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:checked="true"
+ android:text="@string/navigation_bars_toggle_button"
+ android:textOff="@string/navigation_bars_invisible"
+ android:textOn="@string/navigation_bars_visible" />
+
+ <SeekBar
+ android:id="@+id/seekBarNavigation"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="20dp"
+ android:max="10000"
+ android:progress="10000" />
+
+ <ToggleButton
+ android:id="@+id/toggleButtonIme"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:checked="true"
+ android:text="@string/ime_toggle_button"
+ android:textOff="@string/ime_invisible"
+ android:textOn="@string/ime_visible" />
+
+ <SeekBar
+ android:id="@+id/seekBarIme"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="20dp"
+ android:max="10000"
+ android:progress="0" />
+
+ <TextView
+ android:id="@+id/textViewControllableInsets"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="5dp" />
+
+ <EditText
+ android:id="@+id/editText"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ems="10"
+ android:autofillHints="@string/for_testing_ime"
+ android:hint="@string/for_testing_ime"
+ android:inputType="text"
+ android:text="" />
+
+ </LinearLayout>
+
+ </ScrollView>
+
+</LinearLayout>
diff --git a/tests/WindowInsetsTests/res/layout/main_activity.xml b/tests/WindowInsetsTests/res/layout/main_activity.xml
index 621ed89204d1..d6d4ff9ca0a9 100644
--- a/tests/WindowInsetsTests/res/layout/main_activity.xml
+++ b/tests/WindowInsetsTests/res/layout/main_activity.xml
@@ -16,22 +16,38 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
- <Button
- android:id="@+id/chat_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/chat_activity_title"
- android:textAllCaps="false"/>
+ <androidx.appcompat.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ />
- <Button
- android:id="@+id/controller_button"
- android:layout_width="wrap_content"
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:text="@string/controller_activity_title"
- android:textAllCaps="false"/>
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp">
+
+ <Button
+ android:id="@+id/chat_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/chat_activity_title"
+ android:textAllCaps="false"/>
+
+ <Button
+ android:id="@+id/controller_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/controller_activity_title"
+ android:textAllCaps="false"/>
+
+ </LinearLayout>
</LinearLayout>
diff --git a/tests/WindowInsetsTests/res/values-night/styles.xml b/tests/WindowInsetsTests/res/values-night/styles.xml
new file mode 100644
index 000000000000..323c5fd9698e
--- /dev/null
+++ b/tests/WindowInsetsTests/res/values-night/styles.xml
@@ -0,0 +1,43 @@
+<?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>
+
+ <style name="base" parent="@style/Theme.MaterialComponents">
+ <item name="windowActionBar">false</item>
+ <item name="windowNoTitle">true</item>
+
+ <item name="colorPrimary">@color/primaryColor</item>
+ <item name="colorPrimaryDark">@color/primaryDarkColor</item>
+ <item name="colorSecondary">?attr/colorPrimary</item>
+ <item name="colorOnSecondary">@color/primaryTextColor</item>
+
+ <!-- Window decor -->
+ <item name="android:windowLightStatusBar">false</item>
+ <item name="android:windowLightNavigationBar">false</item>
+
+ </style>
+
+ <color name="primaryColor">#639ff9</color>
+ <color name="primaryLightColor">#6f6bff</color>
+ <color name="primaryDarkColor">#0016bb</color>
+ <color name="primaryTextColor">#ffffff</color>
+
+ <color name="bubble">#333333</color>
+ <color name="bubble_self">#185abc</color>
+
+</resources>
diff --git a/tests/WindowInsetsTests/res/values/strings.xml b/tests/WindowInsetsTests/res/values/strings.xml
index 516d4584426e..7b70852e6082 100644
--- a/tests/WindowInsetsTests/res/values/strings.xml
+++ b/tests/WindowInsetsTests/res/values/strings.xml
@@ -19,6 +19,16 @@
<string name="application_title">Window Insets Tests</string>
<string name="chat_activity_title">New Insets Chat</string>
<string name="controller_activity_title">Window Insets Controller</string>
+ <string name="status_bars_toggle_button">Status Bars Toggle Button</string>
+ <string name="status_bars_invisible">Status Bars Invisible</string>
+ <string name="status_bars_visible">Status Bars Visible</string>
+ <string name="navigation_bars_toggle_button">Navigation Bars Toggle Button</string>
+ <string name="navigation_bars_invisible">Navigation Bars Invisible</string>
+ <string name="navigation_bars_visible">Navigation Bars Visible</string>
+ <string name="ime_toggle_button">IME Bars Toggle Button</string>
+ <string name="ime_invisible">IME Bars Invisible</string>
+ <string name="ime_visible">IME Bars Visible</string>
+ <string name="for_testing_ime">For testing IME&#8230;</string>
<!-- The item positions should match the flag values respectively. -->
<string-array name="behaviors">
diff --git a/tests/WindowInsetsTests/res/values/styles.xml b/tests/WindowInsetsTests/res/values/styles.xml
index a84ffbed600d..4ce6323d8189 100644
--- a/tests/WindowInsetsTests/res/values/styles.xml
+++ b/tests/WindowInsetsTests/res/values/styles.xml
@@ -17,7 +17,7 @@
<resources>
- <style name="chat" parent="@style/Theme.MaterialComponents.Light">
+ <style name="base" parent="@style/Theme.MaterialComponents.Light">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
@@ -27,10 +27,8 @@
<item name="colorOnSecondary">@color/primaryTextColor</item>
<!-- Window decor -->
- <item name="android:statusBarColor">#ffffff</item>
<item name="android:windowLightStatusBar">true</item>
<item name="android:windowLightNavigationBar">true</item>
- <item name="android:navigationBarColor">#ffffff</item>
</style>
@@ -63,11 +61,4 @@
<dimen name="bubble_padding">8dp</dimen>
<dimen name="bubble_padding_side">16dp</dimen>
- <style name="controller" parent="android:Theme.Material">
- <item name="android:colorPrimaryDark">#111111</item>
- <item name="android:navigationBarColor">#111111</item>
- <item name="android:colorPrimary">#222222</item>
- <item name="android:colorAccent">#33ccff</item>
- </style>
-
-</resources> \ No newline at end of file
+</resources>
diff --git a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java
index 167d560633ab..1dd87dfd3977 100644
--- a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java
+++ b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java
@@ -16,12 +16,18 @@
package com.google.android.test.windowinsetstests;
-import android.app.Activity;
+import static android.view.WindowInsets.Type.displayCutout;
+import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowInsets.Type.statusBars;
+import static android.view.WindowInsets.Type.systemBars;
+import static android.view.WindowInsets.Type.systemGestures;
+
import android.graphics.Insets;
import android.os.Bundle;
import android.view.View;
import android.view.WindowInsets;
-import android.view.WindowInsets.Type;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimationControlListener;
import android.view.WindowInsetsAnimationController;
import android.widget.AdapterView;
@@ -32,7 +38,9 @@ import android.widget.Spinner;
import android.widget.TextView;
import android.widget.ToggleButton;
-public class ControllerActivity extends Activity implements View.OnApplyWindowInsetsListener {
+import androidx.appcompat.app.AppCompatActivity;
+
+public class ControllerActivity extends AppCompatActivity {
private ToggleButton mToggleStatus;
private SeekBar mSeekStatus;
@@ -48,6 +56,29 @@ public class ControllerActivity extends Activity implements View.OnApplyWindowIn
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.controller_activity);
+ setSupportActionBar(findViewById(R.id.toolbar));
+ getWindow().setDecorFitsSystemWindows(false);
+ findViewById(R.id.root).setOnApplyWindowInsetsListener(
+ (v, insets) -> {
+ final int visibleTypes = systemBars() | displayCutout();
+ final Insets i = insets.getInsets(visibleTypes);
+ v.setPadding(i.left, i.top, i.right, i.bottom);
+
+ // Make the content view not obscured by gesture insets to prevent triggering
+ // system gestures while controlling seek bars.
+ final Insets gi = Insets.subtract(
+ insets.getInsets(systemGestures() | visibleTypes), i);
+ findViewById(R.id.content).setPadding(gi.left, gi.top, gi.right, gi.bottom);
+
+ mNotFromUser[0] = true;
+ updateWidgets(insets, statusBars(), mToggleStatus, mSeekStatus);
+ updateWidgets(insets, navigationBars(), mToggleNavigation, mSeekNavigation);
+ updateWidgets(insets, ime(), mToggleIme, mSeekIme);
+ mLastInsets = insets;
+ mNotFromUser[0] = false;
+
+ return WindowInsets.CONSUMED;
+ });
final Spinner spinnerBehavior = findViewById(R.id.spinnerBehavior);
ArrayAdapter<CharSequence> adapterBehavior = ArrayAdapter.createFromResource(this,
R.array.behaviors, android.R.layout.simple_spinner_item);
@@ -66,23 +97,21 @@ public class ControllerActivity extends Activity implements View.OnApplyWindowIn
});
mToggleStatus = findViewById(R.id.toggleButtonStatus);
mToggleStatus.setTag(mNotFromUser);
- mToggleStatus.setOnCheckedChangeListener(new ToggleListener(Type.statusBars()));
+ mToggleStatus.setOnCheckedChangeListener(new ToggleListener(statusBars()));
mSeekStatus = findViewById(R.id.seekBarStatus);
- mSeekStatus.setOnSeekBarChangeListener(new SeekBarListener(Type.statusBars()));
+ mSeekStatus.setOnSeekBarChangeListener(new SeekBarListener(statusBars()));
mToggleNavigation = findViewById(R.id.toggleButtonNavigation);
mToggleNavigation.setTag(mNotFromUser);
- mToggleNavigation.setOnCheckedChangeListener(new ToggleListener(Type.navigationBars()));
+ mToggleNavigation.setOnCheckedChangeListener(new ToggleListener(navigationBars()));
mSeekNavigation = findViewById(R.id.seekBarNavigation);
- mSeekNavigation.setOnSeekBarChangeListener(new SeekBarListener(Type.navigationBars()));
+ mSeekNavigation.setOnSeekBarChangeListener(new SeekBarListener(navigationBars()));
mToggleIme = findViewById(R.id.toggleButtonIme);
mToggleIme.setTag(mNotFromUser);
- mToggleIme.setOnCheckedChangeListener(new ToggleListener(Type.ime()));
+ mToggleIme.setOnCheckedChangeListener(new ToggleListener(ime()));
mSeekIme = findViewById(R.id.seekBarIme);
- mSeekIme.setOnSeekBarChangeListener(new SeekBarListener(Type.ime()));
+ mSeekIme.setOnSeekBarChangeListener(new SeekBarListener(ime()));
mTextControllableInsets = findViewById(R.id.textViewControllableInsets);
- final View contentView = findViewById(R.id.content);
- contentView.setOnApplyWindowInsetsListener(this);
- contentView.getWindowInsetsController().addOnControllableInsetsChangedListener(
+ mTextControllableInsets.getWindowInsetsController().addOnControllableInsetsChangedListener(
(c, types) -> mTextControllableInsets.setText(
"ControllableInsetsTypes:\n" + insetsTypesToString(types)));
}
@@ -91,22 +120,6 @@ public class ControllerActivity extends Activity implements View.OnApplyWindowIn
return types == 0 ? "none" : WindowInsets.Type.toString(types);
}
- @Override
- public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
- mNotFromUser[0] = true;
- updateWidgets(insets, Type.statusBars(), mToggleStatus, mSeekStatus);
- updateWidgets(insets, Type.navigationBars(), mToggleNavigation, mSeekNavigation);
- updateWidgets(insets, Type.ime(), mToggleIme, mSeekIme);
- mLastInsets = insets;
- mNotFromUser[0] = false;
-
- // Prevent triggering system gestures while controlling seek bars.
- final Insets gestureInsets = insets.getInsets(Type.systemGestures());
- v.setPadding(gestureInsets.left, 0, gestureInsets.right, 0);
-
- return v.onApplyWindowInsets(insets);
- }
-
private void updateWidgets(WindowInsets insets, int types, ToggleButton toggle, SeekBar seek) {
final boolean isVisible = insets.isVisible(types);
final boolean wasVisible = mLastInsets != null ? mLastInsets.isVisible(types) : !isVisible;
@@ -121,7 +134,7 @@ public class ControllerActivity extends Activity implements View.OnApplyWindowIn
private static class ToggleListener implements CompoundButton.OnCheckedChangeListener {
- private final @Type.InsetsType int mTypes;
+ private final @InsetsType int mTypes;
ToggleListener(int types) {
mTypes = types;
@@ -143,7 +156,7 @@ public class ControllerActivity extends Activity implements View.OnApplyWindowIn
private static class SeekBarListener implements SeekBar.OnSeekBarChangeListener {
- private final @Type.InsetsType int mTypes;
+ private final @InsetsType int mTypes;
private WindowInsetsAnimationController mController;
diff --git a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsTestsMainActivity.java b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsTestsMainActivity.java
index 8b77a78ff51e..278ad845d2bb 100644
--- a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsTestsMainActivity.java
+++ b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsTestsMainActivity.java
@@ -16,16 +16,30 @@
package com.google.android.test.windowinsetstests;
-import android.app.Activity;
+import static android.view.WindowInsets.Type.displayCutout;
+import static android.view.WindowInsets.Type.systemBars;
+
import android.content.Intent;
+import android.graphics.Insets;
import android.os.Bundle;
+import android.view.WindowInsets;
+
+import androidx.appcompat.app.AppCompatActivity;
-public class WindowInsetsTestsMainActivity extends Activity {
+public class WindowInsetsTestsMainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
+ setSupportActionBar(findViewById(R.id.toolbar));
+
+ findViewById(R.id.root).setOnApplyWindowInsetsListener(
+ (v, insets) -> {
+ final Insets i = insets.getInsets(systemBars() | displayCutout());
+ v.setPadding(i.left, i.top, i.right, i.bottom);
+ return WindowInsets.CONSUMED;
+ });
findViewById(R.id.chat_button).setOnClickListener(
v -> startActivity(new Intent(this, ChatActivity.class)));
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
index 6189fb0834d8..edad67896e8e 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
@@ -107,7 +107,6 @@ public abstract class NetworkEvaluationTestBase {
@Mock protected Context mContext;
@Mock protected Network mNetwork;
@Mock protected FeatureFlags mFeatureFlags;
- @Mock protected android.net.platform.flags.FeatureFlags mCoreNetFeatureFlags;
@Mock protected TelephonySubscriptionSnapshot mSubscriptionSnapshot;
@Mock protected ConnectivityManager mConnectivityManager;
@Mock protected TelephonyManager mTelephonyManager;
diff --git a/tools/streaming_proto/java/java_proto_stream_code_generator.cpp b/tools/streaming_proto/java/java_proto_stream_code_generator.cpp
index 9d61111fb5bd..be5c197b7c5b 100644
--- a/tools/streaming_proto/java/java_proto_stream_code_generator.cpp
+++ b/tools/streaming_proto/java/java_proto_stream_code_generator.cpp
@@ -18,11 +18,13 @@
#include <stdio.h>
+#include <algorithm>
#include <iomanip>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
+#include <unordered_set>
#include "Errors.h"
@@ -30,21 +32,39 @@ using namespace android::stream_proto;
using namespace google::protobuf::io;
using namespace std;
+static bool outer_class_name_clashes_with_any_message(const string& outer_class_name,
+ const vector<DescriptorProto>& messages) {
+ return any_of(messages.cbegin(), messages.cend(), [&](const DescriptorProto& message) {
+ return message.name() == outer_class_name;
+ });
+}
+
/**
* If the descriptor gives us a class name, use that. Otherwise make one up from
* the filename of the .proto file.
*/
-static string make_outer_class_name(const FileDescriptorProto& file_descriptor) {
+static string make_outer_class_name(const FileDescriptorProto& file_descriptor,
+ const vector<DescriptorProto>& messages) {
string name = file_descriptor.options().java_outer_classname();
- if (name.size() == 0) {
- name = to_camel_case(file_base_name(file_descriptor.name()));
- if (name.size() == 0) {
- ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE,
- "Unable to make an outer class name for file: %s",
- file_descriptor.name().c_str());
- name = "Unknown";
- }
+ if (!name.empty()) {
+ return name;
}
+
+ // Outer class and messages with the same name would result in invalid java (outer class and
+ // inner class cannot have same names).
+ // If the outer class name clashes with any message, let's append an "OuterClass" suffix.
+ // This behavior is consistent with the standard protoc.
+ name = to_camel_case(file_base_name(file_descriptor.name()));
+ while (outer_class_name_clashes_with_any_message(name, messages)) {
+ name += "OuterClass";
+ }
+
+ if (name.empty()) {
+ ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE, "Unable to make an outer class name for file: %s",
+ file_descriptor.name().c_str());
+ name = "Unknown";
+ }
+
return name;
}
@@ -149,6 +169,12 @@ static void write_message(stringstream& text, const DescriptorProto& message,
write_field(text, message.field(i), indented);
}
+ // Extensions
+ N = message.extension_size();
+ for (int i = 0; i < N; i++) {
+ write_field(text, message.extension(i), indented);
+ }
+
text << indent << "}" << endl;
text << endl;
}
@@ -165,7 +191,7 @@ static void write_file(CodeGeneratorResponse* response, const FileDescriptorProt
stringstream text;
string const package_name = make_java_package(file_descriptor);
- string const outer_class_name = make_outer_class_name(file_descriptor);
+ string const outer_class_name = make_outer_class_name(file_descriptor, messages);
text << "// Generated by protoc-gen-javastream. DO NOT MODIFY." << endl;
text << "// source: " << file_descriptor.name() << endl << endl;
@@ -214,7 +240,7 @@ static void write_file(CodeGeneratorResponse* response, const FileDescriptorProt
*/
static void write_multiple_files(CodeGeneratorResponse* response,
const FileDescriptorProto& file_descriptor,
- set<string> messages_to_compile) {
+ const unordered_set<string>& messages_allowlist) {
// If there is anything to put in the outer class file, create one
if (file_descriptor.enum_type_size() > 0) {
vector<EnumDescriptorProto> enums;
@@ -222,7 +248,7 @@ static void write_multiple_files(CodeGeneratorResponse* response,
for (int i = 0; i < N; i++) {
auto enum_full_name =
file_descriptor.package() + "." + file_descriptor.enum_type(i).name();
- if (!messages_to_compile.empty() && !messages_to_compile.count(enum_full_name)) {
+ if (!messages_allowlist.empty() && !messages_allowlist.count(enum_full_name)) {
continue;
}
enums.push_back(file_descriptor.enum_type(i));
@@ -230,9 +256,10 @@ static void write_multiple_files(CodeGeneratorResponse* response,
vector<DescriptorProto> messages;
- if (messages_to_compile.empty() || !enums.empty()) {
+ if (messages_allowlist.empty() || !enums.empty()) {
write_file(response, file_descriptor,
- make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
+ make_file_name(file_descriptor,
+ make_outer_class_name(file_descriptor, messages)),
true, enums, messages);
}
}
@@ -246,12 +273,12 @@ static void write_multiple_files(CodeGeneratorResponse* response,
auto message_full_name =
file_descriptor.package() + "." + file_descriptor.message_type(i).name();
- if (!messages_to_compile.empty() && !messages_to_compile.count(message_full_name)) {
+ if (!messages_allowlist.empty() && !messages_allowlist.count(message_full_name)) {
continue;
}
messages.push_back(file_descriptor.message_type(i));
- if (messages_to_compile.empty() || !messages.empty()) {
+ if (messages_allowlist.empty() || !messages.empty()) {
write_file(response, file_descriptor,
make_file_name(file_descriptor, file_descriptor.message_type(i).name()),
false, enums, messages);
@@ -261,14 +288,14 @@ static void write_multiple_files(CodeGeneratorResponse* response,
static void write_single_file(CodeGeneratorResponse* response,
const FileDescriptorProto& file_descriptor,
- set<string> messages_to_compile) {
+ const unordered_set<string>& messages_allowlist) {
int N;
vector<EnumDescriptorProto> enums;
N = file_descriptor.enum_type_size();
for (int i = 0; i < N; i++) {
auto enum_full_name = file_descriptor.package() + "." + file_descriptor.enum_type(i).name();
- if (!messages_to_compile.empty() && !messages_to_compile.count(enum_full_name)) {
+ if (!messages_allowlist.empty() && !messages_allowlist.count(enum_full_name)) {
continue;
}
@@ -281,22 +308,23 @@ static void write_single_file(CodeGeneratorResponse* response,
auto message_full_name =
file_descriptor.package() + "." + file_descriptor.message_type(i).name();
- if (!messages_to_compile.empty() && !messages_to_compile.count(message_full_name)) {
+ if (!messages_allowlist.empty() && !messages_allowlist.count(message_full_name)) {
continue;
}
messages.push_back(file_descriptor.message_type(i));
}
- if (messages_to_compile.empty() || !enums.empty() || !messages.empty()) {
+ if (messages_allowlist.empty() || !enums.empty() || !messages.empty()) {
write_file(response, file_descriptor,
- make_file_name(file_descriptor, make_outer_class_name(file_descriptor)), true,
- enums, messages);
+ make_file_name(file_descriptor,
+ make_outer_class_name(file_descriptor, messages)),
+ true, enums, messages);
}
}
static void parse_args_string(stringstream args_string_stream,
- set<string>* messages_to_compile_out) {
+ unordered_set<string>& messages_allowlist_out) {
string line;
while (getline(args_string_stream, line, ';')) {
stringstream line_ss(line);
@@ -305,7 +333,7 @@ static void parse_args_string(stringstream args_string_stream,
if (arg_name == "include_filter") {
string full_message_name;
while (getline(line_ss, full_message_name, ',')) {
- messages_to_compile_out->insert(full_message_name);
+ messages_allowlist_out.insert(full_message_name);
}
} else {
ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE, "Unexpected argument '%s'.", arg_name.c_str());
@@ -316,10 +344,10 @@ static void parse_args_string(stringstream args_string_stream,
CodeGeneratorResponse generate_java_protostream_code(CodeGeneratorRequest request) {
CodeGeneratorResponse response;
- set<string> messages_to_compile;
+ unordered_set<string> messages_allowlist;
auto request_params = request.parameter();
if (!request_params.empty()) {
- parse_args_string(stringstream(request_params), &messages_to_compile);
+ parse_args_string(stringstream(request_params), messages_allowlist);
}
// Build the files we need.
@@ -328,9 +356,9 @@ CodeGeneratorResponse generate_java_protostream_code(CodeGeneratorRequest reques
const FileDescriptorProto& file_descriptor = request.proto_file(i);
if (should_generate_for_file(request, file_descriptor.name())) {
if (file_descriptor.options().java_multiple_files()) {
- write_multiple_files(&response, file_descriptor, messages_to_compile);
+ write_multiple_files(&response, file_descriptor, messages_allowlist);
} else {
- write_single_file(&response, file_descriptor, messages_to_compile);
+ write_single_file(&response, file_descriptor, messages_allowlist);
}
}
}
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index 45ab9863ff73..2ba5705a120b 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -993,6 +993,16 @@ public class WifiNl80211Manager {
* {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
* or {@link #setupInterfaceForSoftApMode(String)}.
*
+ * <p>
+ * When an Access Point’s beacon or probe response includes a Multi-BSSID Element, the
+ * returned scan results should include separate scan result for each BSSID within the
+ * Multi-BSSID Information Element. This includes both transmitted and non-transmitted BSSIDs.
+ * Original Multi-BSSID Element will be included in the Information Elements attached to
+ * each of the scan results.
+ * Note: This is the expected behavior for devices supporting 11ax (WiFi-6) and above, and an
+ * optional requirement for devices running with older WiFi generations.
+ * </p>
+ *
* @param ifaceName Name of the interface.
* @param scanType The type of scan result to be returned, can be
* {@link #SCAN_TYPE_SINGLE_SCAN} or {@link #SCAN_TYPE_PNO_SCAN}.