summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp2
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java26
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/README.md77
-rw-r--r--cmds/locksettings/src/com/android/commands/locksettings/LockSettingsCmd.java32
-rw-r--r--core/java/android/app/ActivityManagerInternal.java16
-rw-r--r--core/java/android/app/ActivityThread.java7
-rw-r--r--core/java/android/app/Instrumentation.java87
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java8
-rw-r--r--core/java/android/app/servertransaction/LaunchActivityItem.java21
-rw-r--r--core/java/android/content/pm/parsing/ApkLiteParseUtils.java3
-rw-r--r--core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java28
-rw-r--r--core/java/android/hardware/camera2/CaptureRequest.java5
-rw-r--r--core/java/android/hardware/input/IInputManager.aidl7
-rw-r--r--core/java/android/hardware/input/InputManager.java54
-rw-r--r--core/java/android/net/TEST_MAPPING2
-rw-r--r--core/java/android/os/UserManager.java6
-rw-r--r--core/java/android/service/dreams/DreamService.java4
-rw-r--r--core/java/android/util/NtpTrustedTime.java10
-rw-r--r--core/java/android/view/Display.java6
-rw-r--r--core/java/android/view/DisplayInfo.java13
-rw-r--r--core/java/android/view/View.java2
-rw-r--r--core/java/android/view/ViewRootImpl.java44
-rw-r--r--core/java/com/android/internal/app/ChooserActivity.java16
-rw-r--r--core/java/com/android/internal/app/ResolverActivity.java14
-rw-r--r--core/java/com/android/server/SystemConfig.java94
-rw-r--r--core/proto/android/os/appbackgroundrestrictioninfo.proto199
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java2
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/TestUtils.java8
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java1
-rw-r--r--core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java2
-rw-r--r--data/etc/privapp-permissions-platform.xml1
-rw-r--r--framework-jarjar-rules.txt3
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java42
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java2
-rw-r--r--libs/WindowManager/Jetpack/tests/OWNERS2
-rw-r--r--libs/WindowManager/Shell/res/drawable/home_icon.xml45
-rw-r--r--libs/WindowManager/Shell/res/drawable/tv_pip_menu_background.xml23
-rw-r--r--libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml23
-rw-r--r--libs/WindowManager/Shell/res/layout/tv_pip_menu.xml63
-rw-r--r--libs/WindowManager/Shell/res/layout/tv_pip_menu_background.xml28
-rw-r--r--libs/WindowManager/Shell/res/values-tvdpi/dimen.xml16
-rw-r--r--libs/WindowManager/Shell/res/values/colors_tv.xml6
-rw-r--r--libs/WindowManager/Shell/res/values/styles.xml9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java44
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/CenteredImageSpan.java76
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java32
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt70
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java250
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java171
-rw-r--r--libs/WindowManager/Shell/tests/OWNERS2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt27
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt1
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java13
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java15
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java96
-rw-r--r--location/java/android/location/LocationDeviceConfig.java32
-rw-r--r--media/java/android/media/AudioManager.java10
-rw-r--r--media/tests/aidltests/Android.bp1
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java42
-rw-r--r--packages/Shell/AndroidManifest.xml3
-rw-r--r--packages/SystemUI/docs/device-entry/bouncer.md (renamed from packages/SystemUI/docs/keyguard/bouncer.md)2
-rw-r--r--packages/SystemUI/docs/device-entry/doze.md (renamed from packages/SystemUI/docs/keyguard/doze.md)2
-rw-r--r--packages/SystemUI/docs/device-entry/glossary.md48
-rw-r--r--packages/SystemUI/docs/device-entry/imgs/aod.pngbin0 -> 11001 bytes
-rw-r--r--packages/SystemUI/docs/device-entry/imgs/bouncer_pin.pngbin0 -> 16086 bytes
-rw-r--r--packages/SystemUI/docs/device-entry/imgs/bypass.pngbin0 -> 33963 bytes
-rw-r--r--packages/SystemUI/docs/device-entry/imgs/lockscreen.pngbin0 -> 40789 bytes
-rw-r--r--packages/SystemUI/docs/device-entry/keyguard.md (renamed from packages/SystemUI/docs/keyguard.md)17
-rw-r--r--packages/SystemUI/docs/user-switching.md2
-rw-r--r--packages/SystemUI/res-keyguard/layout/fgs_footer.xml7
-rw-r--r--packages/SystemUI/res-keyguard/layout/footer_actions.xml111
-rw-r--r--packages/SystemUI/res-keyguard/layout/new_footer_actions.xml97
-rw-r--r--packages/SystemUI/res/drawable/fgs_dot.xml (renamed from packages/SystemUI/res/drawable/new_fgs_dot.xml)0
-rw-r--r--packages/SystemUI/res/drawable/ic_media_connecting_container.xml40
-rw-r--r--packages/SystemUI/res/drawable/qs_footer_action_chip_background.xml42
-rw-r--r--packages/SystemUI/res/drawable/qs_footer_action_chip_background_borderless.xml35
-rw-r--r--packages/SystemUI/res/drawable/qs_footer_action_circle.xml2
-rw-r--r--packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml2
-rw-r--r--packages/SystemUI/res/layout/qs_footer_impl.xml15
-rw-r--r--packages/SystemUI/res/layout/qs_panel.xml9
-rw-r--r--packages/SystemUI/res/layout/quick_settings_security_footer.xml19
-rw-r--r--packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml12
-rw-r--r--packages/SystemUI/res/layout/screenshot_static.xml1
-rw-r--r--packages/SystemUI/res/values/attrs.xml6
-rw-r--r--packages/SystemUI/res/values/colors.xml4
-rw-r--r--packages/SystemUI/res/values/dimens.xml13
-rw-r--r--packages/SystemUI/res/values/ids.xml3
-rw-r--r--packages/SystemUI/res/values/strings.xml2
-rw-r--r--packages/SystemUI/res/values/styles.xml10
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt1
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java82
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaData.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt54
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManager.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java49
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt78
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFragment.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanel.java62
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java76
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java82
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java138
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/DualHeightHorizontalLinearLayout.kt183
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt37
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt54
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java97
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt109
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt51
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt28
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt28
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt169
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java52
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java4
-rw-r--r--services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java10
-rw-r--r--services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java67
-rw-r--r--services/core/Android.bp4
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java12
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java27
-rw-r--r--services/core/java/com/android/server/am/AppBatteryTracker.java91
-rw-r--r--services/core/java/com/android/server/am/AppFGSTracker.java24
-rw-r--r--services/core/java/com/android/server/am/AppRestrictionController.java3
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueue.java6
-rw-r--r--services/core/java/com/android/server/am/DropboxRateLimiter.java125
-rw-r--r--services/core/java/com/android/server/am/ServiceRecord.java9
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java6
-rw-r--r--services/core/java/com/android/server/audio/SpatializerHelper.java25
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java20
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerShellCommand.java16
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplay.java2
-rw-r--r--services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java103
-rw-r--r--services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java67
-rw-r--r--services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapperInterface.java61
-rw-r--r--services/core/java/com/android/server/hdmi/AudioStatus.java67
-rw-r--r--services/core/java/com/android/server/hdmi/Constants.java3
-rwxr-xr-xservices/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java66
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java1
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java23
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecMessage.java2
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecNetwork.java7
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java434
-rw-r--r--services/core/java/com/android/server/hdmi/SendKeyAction.java15
-rw-r--r--services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java7
-rw-r--r--services/core/java/com/android/server/hdmi/SystemAudioAction.java7
-rw-r--r--services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java4
-rw-r--r--services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java112
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java362
-rw-r--r--services/core/java/com/android/server/input/NativeInputManagerService.java389
-rw-r--r--services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java16
-rw-r--r--services/core/java/com/android/server/locales/LocaleManagerService.java24
-rw-r--r--services/core/java/com/android/server/locales/OWNERS1
-rw-r--r--services/core/java/com/android/server/location/LocationManagerService.java17
-rw-r--r--services/core/java/com/android/server/location/injector/SettingsHelper.java14
-rw-r--r--services/core/java/com/android/server/location/injector/SystemSettingsHelper.java30
-rw-r--r--services/core/java/com/android/server/location/provider/LocationProviderManager.java17
-rw-r--r--services/core/java/com/android/server/logcat/LogcatManagerService.java3
-rw-r--r--services/core/java/com/android/server/media/BluetoothRouteProvider.java4
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java4
-rw-r--r--services/core/java/com/android/server/media/SystemMediaRoute2Provider.java21
-rw-r--r--services/core/java/com/android/server/pm/AppsFilter.java338
-rw-r--r--services/core/java/com/android/server/pm/BroadcastHelper.java12
-rw-r--r--services/core/java/com/android/server/pm/DexOptHelper.java26
-rw-r--r--services/core/java/com/android/server/pm/InitAppsHelper.java4
-rw-r--r--services/core/java/com/android/server/pm/IntentResolverInterceptor.java64
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java2
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java306
-rw-r--r--services/core/java/com/android/server/pm/dex/ArtManagerService.java40
-rw-r--r--services/core/java/com/android/server/pm/parsing/library/ApexSharedLibraryUpdater.java23
-rw-r--r--services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java13
-rw-r--r--services/core/java/com/android/server/policy/AppOpsPolicy.java8
-rw-r--r--services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java10
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java223
-rw-r--r--services/core/java/com/android/server/vibrator/Vibration.java1
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java10
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java3
-rw-r--r--services/core/java/com/android/server/wm/InputManagerCallback.java4
-rw-r--r--services/core/java/com/android/server/wm/InputMonitor.java2
-rw-r--r--services/core/java/com/android/server/wm/SurfaceAnimationRunner.java13
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java13
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp523
-rw-r--r--services/devicepolicy/TEST_MAPPING10
-rw-r--r--services/midi/java/com/android/server/midi/MidiService.java12
-rw-r--r--services/people/java/com/android/server/people/data/DataManager.java27
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java12
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java148
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/injector/FakeSettingsHelper.java21
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java17
-rw-r--r--services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java87
-rw-r--r--services/tests/servicestests/src/com/android/server/backup/restore/FullRestoreEngineTest.java150
-rw-r--r--services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java570
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/FakeAudioDeviceVolumeManagerWrapper.java72
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java56
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToAudioSystemAvcTest.java107
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToTvAvcTest.java110
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvcTest.java66
-rw-r--r--services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java31
-rw-r--r--services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java51
-rw-r--r--services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java98
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java117
-rw-r--r--services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java150
-rw-r--r--services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java313
-rw-r--r--services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/soundtrigger_middleware/TestUtil.java23
-rw-r--r--services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java54
-rw-r--r--services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java28
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java24
-rw-r--r--telecomm/java/android/telecom/TelecomManager.java27
-rw-r--r--telecomm/java/com/android/internal/telecom/ITelecomService.aidl7
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java8
-rw-r--r--tests/FlickerTests/OWNERS2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt60
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt135
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt10
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt (renamed from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTestShellTransit.kt)16
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt7
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest_ShellTransit.kt80
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt8
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt3
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt8
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt (renamed from tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest_ShellTransit.kt)26
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt8
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt55
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt4
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt16
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt8
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest_ShellTransit.kt82
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml11
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java7
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java46
-rw-r--r--tools/aapt2/Android.bp1
313 files changed, 8280 insertions, 3448 deletions
diff --git a/Android.bp b/Android.bp
index cd110deb8a17..df6fdaa5fdf6 100644
--- a/Android.bp
+++ b/Android.bp
@@ -65,7 +65,6 @@ filegroup {
// Java/AIDL sources under frameworks/base
":framework-annotations",
":framework-blobstore-sources",
- ":framework-connectivity-tiramisu-sources",
":framework-core-sources",
":framework-drm-sources",
":framework-graphics-nonupdatable-sources",
@@ -324,6 +323,7 @@ java_defaults {
"av-types-aidl-java",
"tv_tuner_resource_manager_aidl_interface-java",
"soundtrigger_middleware-aidl-java",
+ "modules-utils-build",
"modules-utils-preconditions",
"modules-utils-synchronous-result-receiver",
"modules-utils-os",
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 f26e051581f3..c6ba1eac56c0 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
@@ -307,7 +307,7 @@ public final class QuotaController extends StateController {
private final SparseBooleanArray mTempAllowlistCache = new SparseBooleanArray();
/**
- * Mapping of UIDs to the when their temp allowlist grace period ends (in the elapsed
+ * Mapping of UIDs to when their temp allowlist grace period ends (in the elapsed
* realtime timebase).
*/
private final SparseLongArray mTempAllowlistGraceCache = new SparseLongArray();
@@ -815,6 +815,19 @@ public final class QuotaController extends StateController {
jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
}
+ private boolean hasTempAllowlistExemptionLocked(int sourceUid, int standbyBucket,
+ long nowElapsed) {
+ if (standbyBucket == RESTRICTED_INDEX || standbyBucket == NEVER_INDEX) {
+ // Don't let RESTRICTED apps get free quota from the temp allowlist.
+ // TODO: consider granting the exemption to RESTRICTED apps if the temp allowlist allows
+ // them to start FGS
+ return false;
+ }
+ final long tempAllowlistGracePeriodEndElapsed = mTempAllowlistGraceCache.get(sourceUid);
+ return mTempAllowlistCache.get(sourceUid)
+ || nowElapsed < tempAllowlistGracePeriodEndElapsed;
+ }
+
/** @return true if the job is within expedited job quota. */
@GuardedBy("mLock")
public boolean isWithinEJQuotaLocked(@NonNull final JobStatus jobStatus) {
@@ -833,11 +846,8 @@ public final class QuotaController extends StateController {
}
final long nowElapsed = sElapsedRealtimeClock.millis();
- final long tempAllowlistGracePeriodEndElapsed =
- mTempAllowlistGraceCache.get(jobStatus.getSourceUid());
- final boolean hasTempAllowlistExemption = mTempAllowlistCache.get(jobStatus.getSourceUid())
- || nowElapsed < tempAllowlistGracePeriodEndElapsed;
- if (hasTempAllowlistExemption) {
+ if (hasTempAllowlistExemptionLocked(jobStatus.getSourceUid(),
+ jobStatus.getEffectiveStandbyBucket(), nowElapsed)) {
return true;
}
@@ -2127,10 +2137,8 @@ public final class QuotaController extends StateController {
final long nowElapsed = sElapsedRealtimeClock.millis();
final int standbyBucket = JobSchedulerService.standbyBucketForPackage(mPkg.packageName,
mPkg.userId, nowElapsed);
- final long tempAllowlistGracePeriodEndElapsed = mTempAllowlistGraceCache.get(mUid);
final boolean hasTempAllowlistExemption = !mRegularJobTimer
- && (mTempAllowlistCache.get(mUid)
- || nowElapsed < tempAllowlistGracePeriodEndElapsed);
+ && hasTempAllowlistExemptionLocked(mUid, standbyBucket, nowElapsed);
final long topAppGracePeriodEndElapsed = mTopAppGraceCache.get(mUid);
final boolean hasTopAppExemption = !mRegularJobTimer
&& (mTopAppCache.get(mUid) || nowElapsed < topAppGracePeriodEndElapsed);
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/README.md b/apex/jobscheduler/service/java/com/android/server/tare/README.md
index a4933a127f13..33eadffb8592 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/README.md
+++ b/apex/jobscheduler/service/java/com/android/server/tare/README.md
@@ -1,10 +1,12 @@
+# Overview
+
Welcome to The Android Resource Economy (TARE for short). If you're reading this, you may be
wondering what all of this code is for and what it means. TARE is an attempt to apply economic
principles to resource (principally battery) management. It acknowledges that battery is a limited
resource on mobile devices and that the system must allocate and apportion those resources
-accordingly. Every action (running a job, firing an alarm, using the network, using the CPU,
-etc.) has a cost. Once that action has been performed and that bit of battery has been drained, it's
-no longer available for someone else (another app) to use until the user charges the device again.
+accordingly. Every action (running a job, firing an alarm, using the network, using the CPU, etc.)
+has a cost. Once that action has been performed and that bit of battery has been drained, it's no
+longer available for someone else (another app) to use until the user charges the device again.
The key tenets of TARE are:
@@ -13,10 +15,22 @@ The key tenets of TARE are:
1. Reward for good actions --- reward and encourage behavior that provides value to the user
1. Fine bad actions --- fine and discourage behavior that is bad for the user
-# Details
+In an ideal world, the system could be said to most efficiently allocate resources by maximizing its
+profits &mdash; by maximizing the aggregate sum of the difference between an action's price (that
+the app ends up paying) and the cost to produce by the system. This assumes that more important
+actions have a higher price than less important actions. With this assumption, maximizing profits
+implies that the system runs the most important work first and proceeds in decreasing order of
+importance. Of course, that also means the system will not run anything where an app would pay less
+for the action than the system's cost to produce that action. Some of this breaks down when we throw
+TOP apps into the mix &mdash; TOP apps pay 0 for all actions, even though the CTP may be greater
+than 0. This is to ensure ideal user experience for the app the user is actively interacting with.
+Similar caveats exist for system-critical processes (such as the OS itself) and apps running
+foreground services (since those could be critical to user experience, as is the case for media and
+navigation apps). Excluding those caveats/special situations, maximizing profits of actions
+performed by apps in the background should be the target.
-To achieve the goal laid out by TARE, we introduce the concept of Android Resource Credits
-(ARCs for short).
+To achieve the goal laid out by TARE, we use Android Resource Credits (ARCs for short) as the
+internal/representative currency of the system.
## How do ARCs work?
@@ -36,6 +50,57 @@ all of its ARCs for jobs if it doesn't want to schedule any alarms.
With the ARC system, we can limit the total number of ARCs in circulation, thus limiting how much
total work can be done, regardless of how many apps the user has installed.
+## EconomicPolicy
+
+An EconomicPolicy defines the actions and rewards a specific subsystem makes use of. Each subsystem
+will likely have a unique set of actions that apps can perform, and may choose to reward apps for
+certain behaviors. Generally, the app should be rewarded with ARCs for behaviors that indicate that
+the app provided value to the user. The current set of behaviors that apps may be rewarded for
+include 1) a user seeing a notification, 2) a user interacting with a notification, 3) the user
+opening the app and/or staying in the app for some period of time, 4) the user interacting with a
+widget, and 5) the user explicitly interacting with the app in some other way. These behaviors may
+change as we determine better ways of identifying providing value to the user and/or user desire for
+the app to perform the actions it's requesting.
+
+### Consumption Limit
+
+The consumption limit represents the maximum amount of resources available to be consumed. When the
+battery is satiated (at 100%), then the amount of resources available to be consumed is equal to the
+consumption limit. Each action has a cost to produce that action. When the action is performed,
+those resources are consumed. Thus, when an action is performed, the action's CTP is deducted from
+the remaining amount of resources available. In keeping with the tenet that resources are limited
+and ARCs are a proxy for battery consumption, the amount of resources available to be consumed are
+adjusted as the battery level changes. That is, the consumption limit is scaled based on the current
+battery level, and if the amount currently available to be consumed is greater than the scaled
+consumption limit, then the available resources are decreased to match the scaled limit.
+
+### Regulation
+
+Regulations are unique events invoked by the ~~government~~ system in order to get the whole economy
+moving smoothly.
+
+# Previous Implementations
+
+## V0
+
+The initial implementation/proposal combined the supply of resources with the allocation in a single
+mechanism. It defined the maximum number of resources (ARCs) available at a time, and then divided
+(allocated) that number among the installed apps, intending to have some left over that could be
+allocated as part of the rewards. There were several problems with that mechanism:
+
+1. Not all apps used their credits, which meant that allocating credits to those packages
+ effectively permanently reduced the number of usable/re-allocatable ARCs.
+1. Having a global maximum circulation spread across multiple apps meant that as more apps were
+ installed, the allocation to each app decreased. Eventually (with enough apps installed), no app
+ would be given enough credits to perform any actions.
+
+These problems effectively meant that misallocation was a big problem, demand wasn't well reflected,
+and some apps may not have been able to perform work even though they otherwise should have been.
+
+Tare Improvement Proposal #1 (TIP1) separated allocation (to apps) from supply (by the system) and
+allowed apps to accrue credits as appropriate while still limiting the total number of credits
+consumed.
+
# Definitions
* ARC: Android Resource Credits are the "currency" units used as an abstraction layer over the real
diff --git a/cmds/locksettings/src/com/android/commands/locksettings/LockSettingsCmd.java b/cmds/locksettings/src/com/android/commands/locksettings/LockSettingsCmd.java
index 6a4a4beaa763..7d9260a77158 100644
--- a/cmds/locksettings/src/com/android/commands/locksettings/LockSettingsCmd.java
+++ b/cmds/locksettings/src/com/android/commands/locksettings/LockSettingsCmd.java
@@ -28,43 +28,13 @@ import java.io.PrintStream;
public final class LockSettingsCmd extends BaseCommand {
- private static final String USAGE =
- "usage: locksettings set-pattern [--old OLD_CREDENTIAL] NEW_PATTERN\n" +
- " locksettings set-pin [--old OLD_CREDENTIAL] NEW_PIN\n" +
- " locksettings set-password [--old OLD_CREDENTIAL] NEW_PASSWORD\n" +
- " locksettings clear [--old OLD_CREDENTIAL]\n" +
- " locksettings verify [--old OLD_CREDENTIAL]\n" +
- " locksettings set-disabled DISABLED\n" +
- " locksettings get-disabled\n" +
- "\n" +
- "flags: \n" +
- " --user USER_ID: specify the user, default value is current user\n" +
- "\n" +
- "locksettings set-pattern: sets a pattern\n" +
- " A pattern is specified by a non-separated list of numbers that index the cell\n" +
- " on the pattern in a 1-based manner in left to right and top to bottom order,\n" +
- " i.e. the top-left cell is indexed with 1, whereas the bottom-right cell\n" +
- " is indexed with 9. Example: 1234\n" +
- "\n" +
- "locksettings set-pin: sets a PIN\n" +
- "\n" +
- "locksettings set-password: sets a password\n" +
- "\n" +
- "locksettings clear: clears the unlock credential\n" +
- "\n" +
- "locksettings verify: verifies the credential and unlocks the user\n" +
- "\n" +
- "locksettings set-disabled: sets whether the lock screen should be disabled\n" +
- "\n" +
- "locksettings get-disabled: retrieves whether the lock screen is disabled\n";
-
public static void main(String[] args) {
(new LockSettingsCmd()).run(args);
}
@Override
public void onShowUsage(PrintStream out) {
- out.println(USAGE);
+ main(new String[] { "help" });
}
@Override
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index c022bf370e7e..9c391abe6204 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -562,14 +562,15 @@ public abstract class ActivityManagerInternal {
public abstract void unregisterProcessObserver(IProcessObserver processObserver);
/**
- * Checks if there is an unfinished instrumentation that targets the given uid.
+ * Gets the uid of the instrumentation source if there is an unfinished instrumentation that
+ * targets the given uid.
*
* @param uid The uid to be checked for
*
- * @return True, if there is an instrumentation whose target application uid matches the given
- * uid, false otherwise
+ * @return the uid of the instrumentation source, if there is an instrumentation whose target
+ * application uid matches the given uid, and {@link android.os.Process#INVALID_UID} otherwise.
*/
- public abstract boolean isUidCurrentlyInstrumented(int uid);
+ public abstract int getInstrumentationSourceUid(int uid);
/** Is this a device owner app? */
public abstract boolean isDeviceOwner(int uid);
@@ -791,10 +792,11 @@ public abstract class ActivityManagerInternal {
*
* @param packageName The package name of the process.
* @param uid The UID of the process.
- * @param foregroundId The current foreground service notification ID, a negative value
- * means this notification is being removed.
+ * @param foregroundId The current foreground service notification ID.
+ * @param canceling The given notification is being canceled.
*/
- void onForegroundServiceNotificationUpdated(String packageName, int uid, int foregroundId);
+ void onForegroundServiceNotificationUpdated(String packageName, int uid, int foregroundId,
+ boolean canceling);
}
/**
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 3b843a9c622a..852dd974ae42 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -536,6 +536,9 @@ public final class ActivityThread extends ClientTransactionHandler
// A reusable token for other purposes, e.g. content capture, translation. It shouldn't be
// used without security checks
public IBinder shareableActivityToken;
+ // The token of the initial TaskFragment that embedded this activity. Do not rely on it
+ // after creation because the activity could be reparented.
+ @Nullable public IBinder mInitialTaskFragmentToken;
int ident;
@UnsupportedAppUsage
Intent intent;
@@ -618,7 +621,8 @@ public final class ActivityThread extends ClientTransactionHandler
PersistableBundle persistentState, List<ResultInfo> pendingResults,
List<ReferrerIntent> pendingNewIntents, ActivityOptions activityOptions,
boolean isForward, ProfilerInfo profilerInfo, ClientTransactionHandler client,
- IBinder assistToken, IBinder shareableActivityToken, boolean launchedFromBubble) {
+ IBinder assistToken, IBinder shareableActivityToken, boolean launchedFromBubble,
+ IBinder initialTaskFragmentToken) {
this.token = token;
this.assistToken = assistToken;
this.shareableActivityToken = shareableActivityToken;
@@ -639,6 +643,7 @@ public final class ActivityThread extends ClientTransactionHandler
compatInfo);
mActivityOptions = activityOptions;
mLaunchedFromBubble = launchedFromBubble;
+ mInitialTaskFragmentToken = initialTaskFragmentToken;
init();
}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 638b30e3b46f..961135f47143 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1058,10 +1058,11 @@ public class Instrumentation {
}
/**
- * Sends the key events corresponding to the text to the app being
- * instrumented.
- *
- * @param text The text to be sent.
+ * Sends the key events that result in the given text being typed into the currently focused
+ * window, and waits for it to be processed.
+ *
+ * @param text The text to be sent.
+ * @see #sendKeySync(KeyEvent)
*/
public void sendStringSync(String text) {
if (text == null) {
@@ -1084,11 +1085,12 @@ public class Instrumentation {
}
/**
- * Send a key event to the currently focused window/view and wait for it to
- * be processed. Finished at some point after the recipient has returned
- * from its event processing, though it may <em>not</em> have completely
- * finished reacting from the event -- for example, if it needs to update
- * its display as a result, it may still be in the process of doing that.
+ * Sends a key event to the currently focused window, and waits for it to be processed.
+ * <p>
+ * This method blocks until the recipient has finished handling the event. Note that the
+ * recipient may <em>not</em> have completely finished reacting from the event when this method
+ * returns. For example, it may still be in the process of updating its display or UI contents
+ * upon reacting to the injected event.
*
* @param event The event to send to the current focus.
*/
@@ -1116,34 +1118,42 @@ public class Instrumentation {
}
/**
- * Sends an up and down key event sync to the currently focused window.
+ * Sends up and down key events with the given key code to the currently focused window, and
+ * waits for it to be processed.
*
- * @param key The integer keycode for the event.
+ * @param keyCode The key code for the events to send.
+ * @see #sendKeySync(KeyEvent)
*/
- public void sendKeyDownUpSync(int key) {
- sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, key));
- sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, key));
+ public void sendKeyDownUpSync(int keyCode) {
+ sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
+ sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
}
/**
- * Higher-level method for sending both the down and up key events for a
- * particular character key code. Equivalent to creating both KeyEvent
- * objects by hand and calling {@link #sendKeySync}. The event appears
- * as if it came from keyboard 0, the built in one.
- *
+ * Sends up and down key events with the given key code to the currently focused window, and
+ * waits for it to be processed.
+ * <p>
+ * Equivalent to {@link #sendKeyDownUpSync(int)}.
+ *
* @param keyCode The key code of the character to send.
+ * @see #sendKeySync(KeyEvent)
*/
public void sendCharacterSync(int keyCode) {
- sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
- sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
+ sendKeyDownUpSync(keyCode);
}
-
+
/**
- * Dispatch a pointer event. Finished at some point after the recipient has
- * returned from its event processing, though it may <em>not</em> have
- * completely finished reacting from the event -- for example, if it needs
- * to update its display as a result, it may still be in the process of
- * doing that.
+ * Dispatches a pointer event into a window owned by the instrumented application, and waits for
+ * it to be processed.
+ * <p>
+ * If the motion event being injected is targeted at a window that is not owned by the
+ * instrumented application, the input injection will fail. See {@link #getUiAutomation()} for
+ * injecting events into all windows.
+ * <p>
+ * This method blocks until the recipient has finished handling the event. Note that the
+ * recipient may <em>not</em> have completely finished reacting from the event when this method
+ * returns. For example, it may still be in the process of updating its display or UI contents
+ * upon reacting to the injected event.
*
* @param event A motion event describing the pointer action. (As noted in
* {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use
@@ -1155,10 +1165,10 @@ public class Instrumentation {
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
}
- syncInputTransactionsAndInjectEvent(event);
+ syncInputTransactionsAndInjectEventIntoSelf(event);
}
- private void syncInputTransactionsAndInjectEvent(MotionEvent event) {
+ private void syncInputTransactionsAndInjectEventIntoSelf(MotionEvent event) {
final boolean syncBefore = event.getAction() == MotionEvent.ACTION_DOWN
|| event.isFromSource(InputDevice.SOURCE_MOUSE);
final boolean syncAfter = event.getAction() == MotionEvent.ACTION_UP;
@@ -1169,8 +1179,9 @@ public class Instrumentation {
.syncInputTransactions(true /*waitForAnimations*/);
}
+ // Direct the injected event into windows owned by the instrumentation target.
InputManager.getInstance().injectInputEvent(
- event, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT);
+ event, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT, Process.myUid());
if (syncAfter) {
WindowManagerGlobal.getWindowManagerService()
@@ -1182,19 +1193,21 @@ public class Instrumentation {
}
/**
- * Dispatch a trackball event. Finished at some point after the recipient has
- * returned from its event processing, though it may <em>not</em> have
- * completely finished reacting from the event -- for example, if it needs
- * to update its display as a result, it may still be in the process of
- * doing that.
- *
+ * Dispatches a trackball event into the currently focused window, and waits for it to be
+ * processed.
+ * <p>
+ * This method blocks until the recipient has finished handling the event. Note that the
+ * recipient may <em>not</em> have completely finished reacting from the event when this method
+ * returns. For example, it may still be in the process of updating its display or UI contents
+ * upon reacting to the injected event.
+ *
* @param event A motion event describing the trackball action. (As noted in
* {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use
* {@link SystemClock#uptimeMillis()} as the timebase.
*/
public void sendTrackballEventSync(MotionEvent event) {
validateNotAppThread();
- if ((event.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) == 0) {
+ if (!event.isFromSource(InputDevice.SOURCE_CLASS_TRACKBALL)) {
event.setSource(InputDevice.SOURCE_TRACKBALL);
}
InputManager.getInstance().injectInputEvent(event,
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 88ffdecfb537..d375a9e1ba26 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1682,7 +1682,7 @@ public class DevicePolicyManager {
public @interface ProvisioningConfiguration {}
/**
- * A String extra holding the provisioning trigger. It could be one of
+ * An int extra holding the provisioning trigger. It could be one of
* {@link #PROVISIONING_TRIGGER_CLOUD_ENROLLMENT}, {@link #PROVISIONING_TRIGGER_QR_CODE},
* {@link #PROVISIONING_TRIGGER_MANAGED_ACCOUNT} or {@link
* #PROVISIONING_TRIGGER_UNSPECIFIED}.
@@ -3298,9 +3298,9 @@ public class DevicePolicyManager {
* Activity action: Starts the device policy management role holder updater.
*
* <p>The activity must handle the device policy management role holder update and set the
- * intent result to either {@link Activity#RESULT_OK} if the update was successful, {@link
- * #RESULT_UPDATE_DEVICE_POLICY_MANAGEMENT_ROLE_HOLDER_RECOVERABLE_ERROR} if it encounters a
- * problem that may be solved by relaunching it again, {@link
+ * intent result to either {@link Activity#RESULT_OK} if the update was successful or not
+ * necessary, {@link #RESULT_UPDATE_DEVICE_POLICY_MANAGEMENT_ROLE_HOLDER_RECOVERABLE_ERROR} if
+ * it encounters a problem that may be solved by relaunching it again, {@link
* #RESULT_UPDATE_DEVICE_POLICY_MANAGEMENT_ROLE_HOLDER_PROVISIONING_DISABLED} if role holder
* provisioning is disabled, or {@link
* #RESULT_UPDATE_DEVICE_POLICY_MANAGEMENT_ROLE_HOLDER_UNRECOVERABLE_ERROR} if it encounters
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index d7e09519bfb7..076dbef9ebc4 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -72,6 +72,7 @@ public class LaunchActivityItem extends ClientTransactionItem {
private IBinder mAssistToken;
private IBinder mShareableActivityToken;
private boolean mLaunchedFromBubble;
+ private IBinder mTaskFragmentToken;
/**
* It is only non-null if the process is the first time to launch activity. It is only an
* optimization for quick look up of the interface so the field is ignored for comparison.
@@ -95,7 +96,8 @@ public class LaunchActivityItem extends ClientTransactionItem {
ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,
mPendingResults, mPendingNewIntents, mActivityOptions, mIsForward, mProfilerInfo,
- client, mAssistToken, mShareableActivityToken, mLaunchedFromBubble);
+ client, mAssistToken, mShareableActivityToken, mLaunchedFromBubble,
+ mTaskFragmentToken);
client.handleLaunchActivity(r, pendingActions, null /* customIntent */);
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -119,7 +121,7 @@ public class LaunchActivityItem extends ClientTransactionItem {
List<ReferrerIntent> pendingNewIntents, ActivityOptions activityOptions,
boolean isForward, ProfilerInfo profilerInfo, IBinder assistToken,
IActivityClientController activityClientController, IBinder shareableActivityToken,
- boolean launchedFromBubble) {
+ boolean launchedFromBubble, IBinder taskFragmentToken) {
LaunchActivityItem instance = ObjectPool.obtain(LaunchActivityItem.class);
if (instance == null) {
instance = new LaunchActivityItem();
@@ -128,7 +130,7 @@ public class LaunchActivityItem extends ClientTransactionItem {
voiceInteractor, procState, state, persistentState, pendingResults,
pendingNewIntents, activityOptions, isForward, profilerInfo, assistToken,
activityClientController, shareableActivityToken,
- launchedFromBubble);
+ launchedFromBubble, taskFragmentToken);
return instance;
}
@@ -136,7 +138,7 @@ public class LaunchActivityItem extends ClientTransactionItem {
@Override
public void recycle() {
setValues(this, null, 0, null, null, null, null, null, null, 0, null, null, null, null,
- null, false, null, null, null, null, false);
+ null, false, null, null, null, null, false, null);
ObjectPool.recycle(this);
}
@@ -166,6 +168,7 @@ public class LaunchActivityItem extends ClientTransactionItem {
dest.writeStrongInterface(mActivityClientController);
dest.writeStrongBinder(mShareableActivityToken);
dest.writeBoolean(mLaunchedFromBubble);
+ dest.writeStrongBinder(mTaskFragmentToken);
}
/** Read from Parcel. */
@@ -184,7 +187,8 @@ public class LaunchActivityItem extends ClientTransactionItem {
in.readStrongBinder(),
IActivityClientController.Stub.asInterface(in.readStrongBinder()),
in.readStrongBinder(),
- in.readBoolean());
+ in.readBoolean(),
+ in.readStrongBinder());
}
public static final @NonNull Creator<LaunchActivityItem> CREATOR =
@@ -222,7 +226,8 @@ public class LaunchActivityItem extends ClientTransactionItem {
&& mIsForward == other.mIsForward
&& Objects.equals(mProfilerInfo, other.mProfilerInfo)
&& Objects.equals(mAssistToken, other.mAssistToken)
- && Objects.equals(mShareableActivityToken, other.mShareableActivityToken);
+ && Objects.equals(mShareableActivityToken, other.mShareableActivityToken)
+ && Objects.equals(mTaskFragmentToken, other.mTaskFragmentToken);
}
@Override
@@ -244,6 +249,7 @@ public class LaunchActivityItem extends ClientTransactionItem {
result = 31 * result + Objects.hashCode(mProfilerInfo);
result = 31 * result + Objects.hashCode(mAssistToken);
result = 31 * result + Objects.hashCode(mShareableActivityToken);
+ result = 31 * result + Objects.hashCode(mTaskFragmentToken);
return result;
}
@@ -291,7 +297,7 @@ public class LaunchActivityItem extends ClientTransactionItem {
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
ActivityOptions activityOptions, boolean isForward, ProfilerInfo profilerInfo,
IBinder assistToken, IActivityClientController activityClientController,
- IBinder shareableActivityToken, boolean launchedFromBubble) {
+ IBinder shareableActivityToken, boolean launchedFromBubble, IBinder taskFragmentToken) {
instance.mIntent = intent;
instance.mIdent = ident;
instance.mInfo = info;
@@ -312,5 +318,6 @@ public class LaunchActivityItem extends ClientTransactionItem {
instance.mActivityClientController = activityClientController;
instance.mShareableActivityToken = shareableActivityToken;
instance.mLaunchedFromBubble = launchedFromBubble;
+ instance.mTaskFragmentToken = taskFragmentToken;
}
}
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 5680bcd2e2e6..cb55e303e778 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -568,7 +568,8 @@ public class ApkLiteParseUtils {
}
ParseResult<Integer> targetResult = FrameworkParsingPackageUtils.computeTargetSdkVersion(
- targetVer, targetCode, SDK_CODENAMES, input);
+ targetVer, targetCode, SDK_CODENAMES, input,
+ /* allowUnknownCodenames= */ false);
if (targetResult.isError()) {
return input.error(targetResult);
}
diff --git a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
index a65b6815f8ad..6d74b819301d 100644
--- a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
@@ -36,6 +36,7 @@ import android.util.Slog;
import android.util.apk.ApkSignatureVerifier;
import com.android.internal.util.ArrayUtils;
+import com.android.modules.utils.build.UnboundedSdkLevel;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
@@ -334,8 +335,9 @@ public class FrameworkParsingPackageUtils {
* If {@code targetCode} is not specified, e.g. the value is {@code null}, then the {@code
* targetVers} will be returned unmodified.
* <p>
- * Otherwise, the behavior varies based on whether the current platform is a pre-release
- * version, e.g. the {@code platformSdkCodenames} array has length > 0:
+ * When {@code allowUnknownCodenames} is false, the behavior varies based on whether the
+ * current platform is a pre-release version, e.g. the {@code platformSdkCodenames} array has
+ * length > 0:
* <ul>
* <li>If this is a pre-release platform and the value specified by
* {@code targetCode} is contained within the array of allowed pre-release
@@ -343,22 +345,32 @@ public class FrameworkParsingPackageUtils {
* <li>If this is a released platform, this method will return -1 to
* indicate that the package is not compatible with this platform.
* </ul>
+ * <p>
+ * When {@code allowUnknownCodenames} is true, any codename that is not known (presumed to be
+ * a codename announced after the build of the current device) is allowed and this method will
+ * return {@link Build.VERSION_CODES#CUR_DEVELOPMENT}.
*
- * @param targetVers targetSdkVersion number, if specified in the application
- * manifest, or 0 otherwise
- * @param targetCode targetSdkVersion code, if specified in the application manifest,
- * or {@code null} otherwise
- * @param platformSdkCodenames array of allowed pre-release SDK codenames for this platform
+ * @param targetVers targetSdkVersion number, if specified in the application
+ * manifest, or 0 otherwise
+ * @param targetCode targetSdkVersion code, if specified in the application manifest,
+ * or {@code null} otherwise
+ * @param platformSdkCodenames array of allowed pre-release SDK codenames for this platform
+ * @param allowUnknownCodenames allow unknown codenames, if true this method will accept unknown
+ * (presumed to be future) codenames
* @return the targetSdkVersion to use at runtime if successful
*/
public static ParseResult<Integer> computeTargetSdkVersion(@IntRange(from = 0) int targetVers,
@Nullable String targetCode, @NonNull String[] platformSdkCodenames,
- @NonNull ParseInput input) {
+ @NonNull ParseInput input, boolean allowUnknownCodenames) {
// If it's a release SDK, return the version number unmodified.
if (targetCode == null) {
return input.success(targetVers);
}
+ if (allowUnknownCodenames && UnboundedSdkLevel.isAtMost(targetCode)) {
+ return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
+ }
+
// If it's a pre-release SDK and the codename matches this platform, it
// definitely targets this SDK.
if (matchTargetCode(platformSdkCodenames, targetCode)) {
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 5df64e3cca9e..d94ad3aa1732 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -623,6 +623,11 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
@Override
public void writeToParcel(Parcel dest, int flags) {
+ if (!mPhysicalCameraSettings.containsKey(mLogicalCameraId)) {
+ throw new IllegalStateException("Physical camera settings map must contain a key for "
+ + "the logical camera id.");
+ }
+
int physicalCameraCount = mPhysicalCameraSettings.size();
dest.writeInt(physicalCameraCount);
//Logical camera id and settings always come first.
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index e1ffd4a6761d..57e84bdb686d 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -57,10 +57,11 @@ interface IInputManager {
// Temporarily changes the pointer speed.
void tryPointerSpeed(int speed);
- // Injects an input event into the system. To inject into windows owned by other
- // applications, the caller must have the INJECT_EVENTS permission.
+ // Injects an input event into the system. The caller must have the INJECT_EVENTS permission.
+ // The caller can target windows owned by a certain UID by providing a valid UID, or by
+ // providing {@link android.os.Process#INVALID_UID} to target all windows.
@UnsupportedAppUsage
- boolean injectInputEvent(in InputEvent ev, int mode);
+ boolean injectInputEvent(in InputEvent ev, int mode, int targetUid);
VerifiedInputEvent verifyInputEvent(in InputEvent ev);
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index c38a847dfb9f..0bcabddddf51 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -45,6 +45,7 @@ import android.os.IVibratorStateListener;
import android.os.InputEventInjectionSync;
import android.os.Looper;
import android.os.Message;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
@@ -1107,14 +1108,18 @@ public final class InputManager {
}
}
-
/**
- * Injects an input event into the event system on behalf of an application.
+ * Injects an input event into the event system, targeting windows owned by the provided uid.
+ *
+ * If a valid targetUid is provided, the system will only consider injecting the input event
+ * into windows owned by the provided uid. If the input event is targeted at a window that is
+ * not owned by the provided uid, input injection will fail and a RemoteException will be
+ * thrown.
+ *
* The synchronization mode determines whether the method blocks while waiting for
* input injection to proceed.
* <p>
- * Requires {@link android.Manifest.permission.INJECT_EVENTS} to inject into
- * windows that are owned by other applications.
+ * Requires the {@link android.Manifest.permission.INJECT_EVENTS} permission.
* </p><p>
* Make sure you correctly set the event time and input source of the event
* before calling this method.
@@ -1125,12 +1130,14 @@ public final class InputManager {
* {@link android.os.InputEventInjectionSync.NONE},
* {@link android.os.InputEventInjectionSync.WAIT_FOR_RESULT}, or
* {@link android.os.InputEventInjectionSync.WAIT_FOR_FINISHED}.
+ * @param targetUid The uid to target, or {@link android.os.Process#INVALID_UID} to target all
+ * windows.
* @return True if input event injection succeeded.
*
* @hide
*/
- @UnsupportedAppUsage
- public boolean injectInputEvent(InputEvent event, int mode) {
+ @RequiresPermission(Manifest.permission.INJECT_EVENTS)
+ public boolean injectInputEvent(InputEvent event, int mode, int targetUid) {
if (event == null) {
throw new IllegalArgumentException("event must not be null");
}
@@ -1141,13 +1148,39 @@ public final class InputManager {
}
try {
- return mIm.injectInputEvent(event, mode);
+ return mIm.injectInputEvent(event, mode, targetUid);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
/**
+ * Injects an input event into the event system on behalf of an application.
+ * The synchronization mode determines whether the method blocks while waiting for
+ * input injection to proceed.
+ * <p>
+ * Requires the {@link android.Manifest.permission.INJECT_EVENTS} permission.
+ * </p><p>
+ * Make sure you correctly set the event time and input source of the event
+ * before calling this method.
+ * </p>
+ *
+ * @param event The event to inject.
+ * @param mode The synchronization mode. One of:
+ * {@link android.os.InputEventInjectionSync.NONE},
+ * {@link android.os.InputEventInjectionSync.WAIT_FOR_RESULT}, or
+ * {@link android.os.InputEventInjectionSync.WAIT_FOR_FINISHED}.
+ * @return True if input event injection succeeded.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.INJECT_EVENTS)
+ @UnsupportedAppUsage
+ public boolean injectInputEvent(InputEvent event, int mode) {
+ return injectInputEvent(event, mode, Process.INVALID_UID);
+ }
+
+ /**
* Verify the details of an {@link android.view.InputEvent} that came from the system.
* If the event did not come from the system, or its details could not be verified, then this
* will return {@code null}. Receiving {@code null} does not mean that the event did not
@@ -1421,8 +1454,11 @@ public final class InputManager {
}
mInputDevices = new SparseArray<InputDevice>();
- for (int i = 0; i < ids.length; i++) {
- mInputDevices.put(ids[i], null);
+ // TODO(b/223905476): remove when the rootcause is fixed.
+ if (ids != null) {
+ for (int i = 0; i < ids.length; i++) {
+ mInputDevices.put(ids[i], null);
+ }
}
}
}
diff --git a/core/java/android/net/TEST_MAPPING b/core/java/android/net/TEST_MAPPING
index a379c33316f0..3df56162bd2c 100644
--- a/core/java/android/net/TEST_MAPPING
+++ b/core/java/android/net/TEST_MAPPING
@@ -17,7 +17,7 @@
"path": "frameworks/opt/net/wifi"
}
],
- "postsubmit": [
+ "presubmit": [
{
"name": "FrameworksCoreTests",
"options": [
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index c4cb3195e485..a64e63eacd56 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -300,6 +300,10 @@ public class UserManager {
* When it is set by any of these owners, it prevents all users from using
* Wi-Fi tethering. Other forms of tethering are not affected.
*
+ * This user restriction disables only Wi-Fi tethering.
+ * Use {@link #DISALLOW_CONFIG_TETHERING} to limit all forms of tethering.
+ * When {@link #DISALLOW_CONFIG_TETHERING} is set, this user restriction becomes obsolete.
+ *
* <p>The default value is <code>false</code>.
*
* <p>Key for user restrictions.
@@ -735,7 +739,7 @@ public class UserManager {
public static final String DISALLOW_CONFIG_DATE_TIME = "no_config_date_time";
/**
- * Specifies if a user is disallowed from configuring Tethering and portable hotspots
+ * Specifies if a user is disallowed from using and configuring Tethering and portable hotspots
* via Settings.
*
* <p>This restriction can only be set by a device owner, a profile owner on the primary
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 001707d228b6..d4f8a3beb89b 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1038,14 +1038,14 @@ public class DreamService extends Service implements Window.Callback {
}
mFinished = true;
+ mOverlayConnection.unbind(this);
+
if (mDreamToken == null) {
Slog.w(mTag, "Finish was called before the dream was attached.");
stopSelf();
return;
}
- mOverlayConnection.unbind(this);
-
try {
// finishSelf will unbind the dream controller from the dream service. This will
// trigger DreamService.this.onDestroy and DreamService.this will die.
diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java
index 01a037ae3495..4e7b3a51d758 100644
--- a/core/java/android/util/NtpTrustedTime.java
+++ b/core/java/android/util/NtpTrustedTime.java
@@ -193,6 +193,16 @@ public class NtpTrustedTime implements TrustedTime {
}
final Network network = connectivityManager.getActiveNetwork();
final NetworkInfo ni = connectivityManager.getNetworkInfo(network);
+
+ // This connectivity check is to avoid performing a DNS lookup for the time server on a
+ // unconnected network. There are races to obtain time in Android when connectivity
+ // changes, which means that forceRefresh() can be called by various components before
+ // the network is actually available. This led in the past to DNS lookup failures being
+ // cached (~2 seconds) thereby preventing the device successfully making an NTP request
+ // when connectivity had actually been established.
+ // A side effect of check is that tests that run a fake NTP server on the device itself
+ // will only be able to use it if the active network is connected, even though loopback
+ // addresses are actually reachable.
if (ni == null || !ni.isConnected()) {
if (LOGD) Log.d(TAG, "forceRefresh: no connectivity");
return false;
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 5f0098c25e55..0c4d9bf08583 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1584,10 +1584,10 @@ public final class Display {
return false;
}
final Configuration config = mResources.getConfiguration();
- // TODO(b/179308296) Temporarily - never report max bounds to only Launcher if the feature
- // is disabled.
+ // TODO(b/179308296) Temporarily exclude Launcher from being given max bounds, by checking
+ // if the caller is the recents component.
return config != null && !config.windowConfiguration.getMaxBounds().isEmpty()
- && (mDisplayInfo.shouldConstrainMetricsForLauncher || !isRecentsComponent());
+ && !isRecentsComponent();
}
/**
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 6917d664327f..9264d2ed42a3 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -306,13 +306,6 @@ public final class DisplayInfo implements Parcelable {
public float brightnessDefault;
/**
- * @hide
- * True if Display#getRealSize and getRealMetrics should be constrained for Launcher, false
- * otherwise.
- */
- public boolean shouldConstrainMetricsForLauncher = false;
-
- /**
* The {@link RoundedCorners} if present, otherwise {@code null}.
*/
@Nullable
@@ -395,7 +388,6 @@ public final class DisplayInfo implements Parcelable {
&& brightnessMaximum == other.brightnessMaximum
&& brightnessDefault == other.brightnessDefault
&& Objects.equals(roundedCorners, other.roundedCorners)
- && shouldConstrainMetricsForLauncher == other.shouldConstrainMetricsForLauncher
&& installOrientation == other.installOrientation;
}
@@ -447,7 +439,6 @@ public final class DisplayInfo implements Parcelable {
brightnessMaximum = other.brightnessMaximum;
brightnessDefault = other.brightnessDefault;
roundedCorners = other.roundedCorners;
- shouldConstrainMetricsForLauncher = other.shouldConstrainMetricsForLauncher;
installOrientation = other.installOrientation;
}
@@ -505,7 +496,6 @@ public final class DisplayInfo implements Parcelable {
for (int i = 0; i < numUserDisabledFormats; i++) {
userDisabledHdrTypes[i] = source.readInt();
}
- shouldConstrainMetricsForLauncher = source.readBoolean();
installOrientation = source.readInt();
}
@@ -561,7 +551,6 @@ public final class DisplayInfo implements Parcelable {
for (int i = 0; i < userDisabledHdrTypes.length; i++) {
dest.writeInt(userDisabledHdrTypes[i]);
}
- dest.writeBoolean(shouldConstrainMetricsForLauncher);
dest.writeInt(installOrientation);
}
@@ -817,8 +806,6 @@ public final class DisplayInfo implements Parcelable {
sb.append(brightnessMaximum);
sb.append(", brightnessDefault ");
sb.append(brightnessDefault);
- sb.append(", shouldConstrainMetricsForLauncher ");
- sb.append(shouldConstrainMetricsForLauncher);
sb.append(", installOrientation ");
sb.append(Surface.rotationToString(installOrientation));
sb.append("}");
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d7940e2de98c..8b9a86b9eec6 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -12033,7 +12033,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
@Nullable
public Rect getHandwritingArea() {
final ListenerInfo info = mListenerInfo;
- if (info != null) {
+ if (info != null && info.mHandwritingArea != null) {
return new Rect(info.mHandwritingArea);
}
return null;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 7b3fed74a9be..1328e66c1815 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1742,7 +1742,7 @@ public final class ViewRootImpl implements ViewParent,
mForceNextWindowRelayout = forceNextWindowRelayout;
mPendingAlwaysConsumeSystemBars = args.argi2 != 0;
- mSyncSeqId = args.argi4;
+ mSyncSeqId = args.argi4 > mSyncSeqId ? args.argi4 : mSyncSeqId;
if (msg == MSG_RESIZED_REPORT) {
reportNextDraw();
@@ -6407,6 +6407,24 @@ public final class ViewRootImpl implements ViewParent,
return FINISH_HANDLED;
}
+ // If the new back dispatch is enabled, intercept KEYCODE_BACK before it reaches the
+ // view tree and invoke the appropriate {@link OnBackInvokedCallback}.
+ if (isBack(event)
+ && mContext != null
+ && WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) {
+ OnBackInvokedCallback topCallback =
+ getOnBackInvokedDispatcher().getTopCallback();
+ if (event.getAction() == KeyEvent.ACTION_UP) {
+ if (topCallback != null) {
+ topCallback.onBackInvoked();
+ return FINISH_HANDLED;
+ }
+ } else {
+ // Drop other actions such as {@link KeyEvent.ACTION_DOWN}.
+ return FINISH_NOT_HANDLED;
+ }
+ }
+
// Deliver the key to the view hierarchy.
if (mView.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
@@ -6416,19 +6434,6 @@ public final class ViewRootImpl implements ViewParent,
return FINISH_NOT_HANDLED;
}
- if (isBack(event)
- && mContext != null
- && WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) {
- // Invoke the appropriate {@link OnBackInvokedCallback} if the new back
- // navigation should be used, and the key event is not handled by anything else.
- OnBackInvokedCallback topCallback =
- getOnBackInvokedDispatcher().getTopCallback();
- if (topCallback != null) {
- topCallback.onBackInvoked();
- return FINISH_HANDLED;
- }
- }
-
// This dispatch is for windows that don't have a Window.Callback. Otherwise,
// the Window.Callback usually will have already called this (see
// DecorView.superDispatchKeyEvent) leaving this call a no-op.
@@ -7986,7 +7991,10 @@ public final class ViewRootImpl implements ViewParent,
insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
mTempControls, mRelayoutBundle);
- mSyncSeqId = mRelayoutBundle.getInt("seqid");
+ final int maybeSyncSeqId = mRelayoutBundle.getInt("seqid");
+ if (maybeSyncSeqId > 0) {
+ mSyncSeqId = maybeSyncSeqId;
+ }
if (mTranslator != null) {
mTranslator.translateRectInScreenToAppWindow(mTmpFrames.frame);
@@ -10763,11 +10771,7 @@ public final class ViewRootImpl implements ViewParent,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
InputDevice.SOURCE_KEYBOARD);
-
- ev.setDisplayId(mContext.getDisplay().getDisplayId());
- if (mView != null) {
- mView.dispatchKeyEvent(ev);
- }
+ enqueueInputEvent(ev);
}
private void registerCompatOnBackInvokedCallback() {
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 88089b5b85f2..d4a8a164803f 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -167,16 +167,6 @@ public class ChooserActivity extends ResolverActivity implements
public static final String EXTRA_PRIVATE_RETAIN_IN_ON_STOP
= "com.android.internal.app.ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP";
- /**
- * Boolean extra added to "unbundled Sharesheet" delegation intents to signal whether the app
- * prediction service is available. Our query of the service <em>availability</em> depends on
- * privileges that are only available in the system, even though the service itself would then
- * be available to the unbundled component. For now, we just include the query result as part of
- * the handover intent.
- * TODO: investigate whether the privileged query is necessary to determine the availability.
- */
- public static final String EXTRA_IS_APP_PREDICTION_SERVICE_AVAILABLE =
- "com.android.internal.app.ChooserActivity.EXTRA_IS_APP_PREDICTION_SERVICE_AVAILABLE";
/**
* Transition name for the first image preview.
@@ -985,6 +975,12 @@ public class ChooserActivity extends ResolverActivity implements
}
@Override
+ protected void onResume() {
+ super.onResume();
+ Log.d(TAG, "onResume: " + getComponentName().flattenToShortString());
+ }
+
+ @Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
ViewPager viewPager = findViewById(R.id.profile_pager);
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index ea8589bddabb..bfd8ff923dc1 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -157,8 +157,6 @@ public class ResolverActivity extends Activity implements
/** See {@link #setRetainInOnStop}. */
private boolean mRetainInOnStop;
- protected static final int REQUEST_CODE_RETURN_FROM_DELEGATE_CHOOSER = 20;
-
private static final String EXTRA_SHOW_FRAGMENT_ARGS = ":settings:show_fragment_args";
private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
private static final String OPEN_LINKS_COMPONENT_KEY = "app_link_state";
@@ -1374,18 +1372,6 @@ public class ResolverActivity extends Activity implements
.write();
}
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- switch (requestCode) {
- case REQUEST_CODE_RETURN_FROM_DELEGATE_CHOOSER:
- // Repeat the delegate's result as our own.
- setResult(resultCode, data);
- finish();
- break;
- default:
- super.onActivityResult(requestCode, resultCode, data);
- }
- }
public void onActivityStarted(TargetInfo cti) {
// Do nothing
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index db41d333e1d4..c825f770c0c3 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -46,6 +46,7 @@ import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.build.UnboundedSdkLevel;
import libcore.io.IoUtils;
import libcore.util.EmptyArray;
@@ -88,8 +89,8 @@ public class SystemConfig {
private static final int ALLOW_HIDDENAPI_WHITELISTING = 0x040;
private static final int ALLOW_ASSOCIATIONS = 0x080;
// ALLOW_OVERRIDE_APP_RESTRICTIONS allows to use "allow-in-power-save-except-idle",
- // "allow-in-power-save", "allow-in-data-usage-save", "allow-unthrottled-location",
- // and "allow-ignore-location-settings".
+ // "allow-in-power-save", "allow-in-data-usage-save","allow-unthrottled-location",
+ // "allow-ignore-location-settings" and "allow-adas-location-settings".
private static final int ALLOW_OVERRIDE_APP_RESTRICTIONS = 0x100;
private static final int ALLOW_IMPLICIT_BROADCASTS = 0x200;
private static final int ALLOW_VENDOR_APEX = 0x400;
@@ -126,7 +127,7 @@ public class SystemConfig {
*
* <p>0 means not specified.
*/
- public final int onBootclasspathSince;
+ public final String onBootclasspathSince;
/**
* SDK version this library was removed from the BOOTCLASSPATH.
@@ -138,7 +139,7 @@ public class SystemConfig {
*
* <p>0 means not specified.
*/
- public final int onBootclasspathBefore;
+ public final String onBootclasspathBefore;
/**
* Declares whether this library can be safely ignored from <uses-library> tags.
@@ -155,19 +156,19 @@ public class SystemConfig {
@VisibleForTesting
public SharedLibraryEntry(String name, String filename, String[] dependencies,
boolean isNative) {
- this(name, filename, dependencies, 0 /* onBootclasspathSince */,
- 0 /* onBootclasspathBefore */, isNative);
+ this(name, filename, dependencies, null /* onBootclasspathSince */,
+ null /* onBootclasspathBefore */, isNative);
}
@VisibleForTesting
public SharedLibraryEntry(String name, String filename, String[] dependencies,
- int onBootclasspathSince, int onBootclassPathBefore) {
- this(name, filename, dependencies, onBootclasspathSince, onBootclassPathBefore,
+ String onBootclasspathSince, String onBootclasspathBefore) {
+ this(name, filename, dependencies, onBootclasspathSince, onBootclasspathBefore,
false /* isNative */);
}
SharedLibraryEntry(String name, String filename, String[] dependencies,
- int onBootclasspathSince, int onBootclasspathBefore, boolean isNative) {
+ String onBootclasspathSince, String onBootclasspathBefore, boolean isNative) {
this.name = name;
this.filename = filename;
this.dependencies = dependencies;
@@ -175,16 +176,14 @@ public class SystemConfig {
this.onBootclasspathBefore = onBootclasspathBefore;
this.isNative = isNative;
- canBeSafelyIgnored = this.onBootclasspathSince != 0
- && isSdkAtLeast(this.onBootclasspathSince);
- }
-
- private static boolean isSdkAtLeast(int level) {
- if ("REL".equals(Build.VERSION.CODENAME)) {
- return Build.VERSION.SDK_INT >= level;
- }
- return level == Build.VERSION_CODES.CUR_DEVELOPMENT
- || Build.VERSION.SDK_INT >= level;
+ // this entry can be ignored if either:
+ // - onBootclasspathSince is set and we are at or past that SDK
+ // - onBootclasspathBefore is set and we are before that SDK
+ canBeSafelyIgnored =
+ (this.onBootclasspathSince != null
+ && UnboundedSdkLevel.isAtLeast(this.onBootclasspathSince))
+ || (this.onBootclasspathBefore != null
+ && !UnboundedSdkLevel.isAtLeast(this.onBootclasspathBefore));
}
}
@@ -234,6 +233,10 @@ public class SystemConfig {
// without throttling, as read from the configuration files.
final ArraySet<String> mAllowUnthrottledLocation = new ArraySet<>();
+ // These are the packages that are allow-listed to be able to retrieve location when
+ // the location state is driver assistance only.
+ final ArrayMap<String, ArraySet<String>> mAllowAdasSettings = new ArrayMap<>();
+
// These are the packages that are white-listed to be able to retrieve location even when user
// location settings are off, for emergency purposes, as read from the configuration files.
final ArrayMap<String, ArraySet<String>> mAllowIgnoreLocationSettings = new ArrayMap<>();
@@ -394,6 +397,10 @@ public class SystemConfig {
return mAllowUnthrottledLocation;
}
+ public ArrayMap<String, ArraySet<String>> getAllowAdasLocationSettings() {
+ return mAllowAdasSettings;
+ }
+
public ArrayMap<String, ArraySet<String>> getAllowIgnoreLocationSettings() {
return mAllowIgnoreLocationSettings;
}
@@ -870,10 +877,8 @@ public class SystemConfig {
String lname = parser.getAttributeValue(null, "name");
String lfile = parser.getAttributeValue(null, "file");
String ldependency = parser.getAttributeValue(null, "dependency");
- int minDeviceSdk = XmlUtils.readIntAttribute(parser, "min-device-sdk",
- 0);
- int maxDeviceSdk = XmlUtils.readIntAttribute(parser, "max-device-sdk",
- 0);
+ String minDeviceSdk = parser.getAttributeValue(null, "min-device-sdk");
+ String maxDeviceSdk = parser.getAttributeValue(null, "max-device-sdk");
if (lname == null) {
Slog.w(TAG, "<" + name + "> without name in " + permFile + " at "
+ parser.getPositionDescription());
@@ -881,15 +886,18 @@ public class SystemConfig {
Slog.w(TAG, "<" + name + "> without file in " + permFile + " at "
+ parser.getPositionDescription());
} else {
- boolean allowedMinSdk = minDeviceSdk <= Build.VERSION.SDK_INT;
+ boolean allowedMinSdk =
+ minDeviceSdk == null || UnboundedSdkLevel.isAtLeast(
+ minDeviceSdk);
boolean allowedMaxSdk =
- maxDeviceSdk == 0 || maxDeviceSdk >= Build.VERSION.SDK_INT;
+ maxDeviceSdk == null || UnboundedSdkLevel.isAtMost(
+ maxDeviceSdk);
final boolean exists = new File(lfile).exists();
if (allowedMinSdk && allowedMaxSdk && exists) {
- int bcpSince = XmlUtils.readIntAttribute(parser,
- "on-bootclasspath-since", 0);
- int bcpBefore = XmlUtils.readIntAttribute(parser,
- "on-bootclasspath-before", 0);
+ String bcpSince = parser.getAttributeValue(null,
+ "on-bootclasspath-since");
+ String bcpBefore = parser.getAttributeValue(null,
+ "on-bootclasspath-before");
SharedLibraryEntry entry = new SharedLibraryEntry(lname, lfile,
ldependency == null
? new String[0] : ldependency.split(":"),
@@ -1007,6 +1015,34 @@ public class SystemConfig {
}
XmlUtils.skipCurrentTag(parser);
} break;
+ case "allow-adas-location-settings" : {
+ if (allowOverrideAppRestrictions) {
+ String pkgname = parser.getAttributeValue(null, "package");
+ String attributionTag = parser.getAttributeValue(null,
+ "attributionTag");
+ if (pkgname == null) {
+ Slog.w(TAG, "<" + name + "> without package in "
+ + permFile + " at " + parser.getPositionDescription());
+ } else {
+ ArraySet<String> tags = mAllowAdasSettings.get(pkgname);
+ if (tags == null || !tags.isEmpty()) {
+ if (tags == null) {
+ tags = new ArraySet<>(1);
+ mAllowAdasSettings.put(pkgname, tags);
+ }
+ if (!"*".equals(attributionTag)) {
+ if ("null".equals(attributionTag)) {
+ attributionTag = null;
+ }
+ tags.add(attributionTag);
+ }
+ }
+ }
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ } break;
case "allow-ignore-location-settings": {
if (allowOverrideAppRestrictions) {
String pkgname = parser.getAttributeValue(null, "package");
diff --git a/core/proto/android/os/appbackgroundrestrictioninfo.proto b/core/proto/android/os/appbackgroundrestrictioninfo.proto
new file mode 100644
index 000000000000..8445641694dc
--- /dev/null
+++ b/core/proto/android/os/appbackgroundrestrictioninfo.proto
@@ -0,0 +1,199 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+package android.os;
+
+option java_multiple_files = true;
+
+// This message is used for statsd logging and should be kept in sync with
+// frameworks/proto_logging/stats/atoms.proto
+/**
+ * Logs information about app background restrictions.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/am/AppRestrictionController.java
+ */
+message AppBackgroundRestrictionsInfo {
+ // the uid of the app.
+ optional int32 uid = 1;
+
+ enum RestrictionLevel {
+ LEVEL_UNKNOWN = 0;
+ LEVEL_UNRESTRICTED = 1;
+ LEVEL_EXEMPTED = 2;
+ LEVEL_ADAPTIVE_BUCKET = 3;
+ LEVEL_RESTRICTED_BUCKET = 4;
+ LEVEL_BACKGROUND_RESTRICTED = 5;
+ LEVEL_HIBERNATION = 6;
+ }
+ // indicates the app background restriction level.
+ optional RestrictionLevel restriction_level = 2;
+
+ enum Threshold {
+ THRESHOLD_UNKNOWN = 0;
+ THRESHOLD_RESTRICTED = 1; // app was background restricted by the system.
+ THRESHOLD_USER = 2; // app was background restricted by user action.
+ }
+ // indicates which threshold caused the app to be put into bg restriction.
+ optional Threshold threshold = 3;
+
+ enum StateTracker {
+ UNKNOWN_TRACKER = 0;
+ BATTERY_TRACKER = 1;
+ BATTERY_EXEMPTION_TRACKER = 2;
+ FGS_TRACKER = 3;
+ MEDIA_SESSION_TRACKER = 4;
+ PERMISSION_TRACKER = 5;
+ BROADCAST_EVENTS_TRACKER = 6;
+ BIND_SERVICE_EVENTS_TRACKER = 7;
+ }
+ // indicates the reason/tracker which caused the app to hit the threshold.
+ optional StateTracker tracker = 4;
+
+ message FgsTrackerInfo {
+ // indicates whether an fgs notification was visible for this app or not.
+ optional bool fgs_notification_visible = 1;
+ // total FGS duration for this app.
+ optional int64 fgs_duration = 2;
+ }
+ optional FgsTrackerInfo fgs_tracker_info = 5;
+
+ message BatteryTrackerInfo {
+ // total battery usage within last 24h (percentage)
+ optional int32 battery_24h = 1;
+ // background battery usage (percentage)
+ optional int32 battery_usage_background = 2;
+ // FGS battery usage (percentage)
+ optional int32 battery_usage_fgs = 3;
+ }
+ optional BatteryTrackerInfo battery_tracker_info = 6;
+
+ message BroadcastEventsTrackerInfo {
+ // the number of broadcasts sent by this app.
+ optional int32 broadcasts_sent = 1;
+ }
+ optional BroadcastEventsTrackerInfo broadcast_events_tracker_info = 7;
+
+ message BindServiceEventsTrackerInfo {
+ // the number of bind service requests by this app.
+ optional int32 bind_service_requests = 1;
+ }
+ optional BindServiceEventsTrackerInfo bind_service_events_tracker_info =
+ 8;
+
+ // The reasons listed below are defined in PowerExemptionManager.java
+ enum ExemptionReason {
+ // range 0-9 is reserved for default reasons
+ REASON_UNKNOWN = 0;
+ REASON_DENIED = 1;
+ REASON_OTHER = 2;
+ // range 10-49 is reserved for BG-FGS-launch allowed proc states
+ REASON_PROC_STATE_PERSISTENT = 10;
+ REASON_PROC_STATE_PERSISTENT_UI = 11;
+ REASON_PROC_STATE_TOP = 12;
+ REASON_PROC_STATE_BTOP = 13;
+ REASON_PROC_STATE_FGS = 14;
+ REASON_PROC_STATE_BFGS = 15;
+ // range 50-99 is reserved for BG-FGS-launch allowed reasons
+ REASON_UID_VISIBLE = 50;
+ REASON_SYSTEM_UID = 51;
+ REASON_ACTIVITY_STARTER = 52;
+ REASON_START_ACTIVITY_FLAG = 53;
+ REASON_FGS_BINDING = 54;
+ REASON_DEVICE_OWNER = 55;
+ REASON_PROFILE_OWNER = 56;
+ REASON_COMPANION_DEVICE_MANAGER = 57;
+ REASON_BACKGROUND_ACTIVITY_PERMISSION = 58;
+ REASON_BACKGROUND_FGS_PERMISSION = 59;
+ REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION = 60;
+ REASON_INSTR_BACKGROUND_FGS_PERMISSION = 61;
+ REASON_SYSTEM_ALERT_WINDOW_PERMISSION = 62;
+ REASON_DEVICE_DEMO_MODE = 63;
+ REASON_ALLOWLISTED_PACKAGE = 65;
+ REASON_APPOP = 66;
+ REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD = 67;
+ REASON_OP_ACTIVATE_VPN = 68;
+ REASON_OP_ACTIVATE_PLATFORM_VPN = 69;
+ REASON_TEMP_ALLOWED_WHILE_IN_USE = 70;
+ REASON_CURRENT_INPUT_METHOD = 71;
+ // range 100-199 is reserved for public reasons
+ REASON_GEOFENCING = 100;
+ REASON_PUSH_MESSAGING = 101;
+ REASON_PUSH_MESSAGING_OVER_QUOTA = 102;
+ REASON_ACTIVITY_RECOGNITION = 103;
+ REASON_ACCOUNT_TRANSFER = 104;
+ // range 200-299 is reserved for broadcast actions
+ REASON_BOOT_COMPLETED = 200;
+ REASON_PRE_BOOT_COMPLETED = 201;
+ REASON_LOCKED_BOOT_COMPLETED = 202;
+ REASON_BLUETOOTH_BROADCAST = 203;
+ REASON_TIMEZONE_CHANGED = 204;
+ REASON_TIME_CHANGED = 205;
+ REASON_LOCALE_CHANGED = 206;
+ REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED = 207;
+ REASON_REFRESH_SAFETY_SOURCES = 208;
+ // range 300-399 is reserved for other internal reasons
+ REASON_SYSTEM_ALLOW_LISTED = 300;
+ REASON_ALARM_MANAGER_ALARM_CLOCK = 301;
+ REASON_ALARM_MANAGER_WHILE_IDLE = 302;
+ REASON_SERVICE_LAUNCH = 303;
+ REASON_KEY_CHAIN = 304;
+ REASON_PACKAGE_VERIFIER = 305;
+ REASON_SYNC_MANAGER = 306;
+ REASON_DOMAIN_VERIFICATION_V1 = 307;
+ REASON_DOMAIN_VERIFICATION_V2 = 308;
+ REASON_VPN = 309;
+ REASON_NOTIFICATION_SERVICE = 310;
+ REASON_PACKAGE_REPLACED = 311;
+ REASON_LOCATION_PROVIDER = 312;
+ REASON_MEDIA_BUTTON = 313;
+ REASON_EVENT_SMS = 314;
+ REASON_EVENT_MMS = 315;
+ REASON_SHELL = 316;
+ REASON_MEDIA_SESSION_CALLBACK = 317;
+ REASON_ROLE_DIALER = 318;
+ REASON_ROLE_EMERGENCY = 319;
+ REASON_SYSTEM_MODULE = 320;
+ REASON_CARRIER_PRIVILEGED_APP = 321;
+ // app requested to be exempt
+ REASON_OPT_OUT_REQUESTED = 1000;
+ }
+ // indicates if the app is exempt from background restrictions and the reason if applicable.
+ optional ExemptionReason exemption_reason = 9;
+
+ enum OptimizationLevel {
+ UNKNOWN = 0;
+ OPTIMIZED = 1;
+ BACKGROUND_RESTRICTED = 2;
+ NOT_OPTIMIZED = 3;
+ }
+ // the user choice for the optimization level of the app.
+ optional OptimizationLevel opt_level = 10;
+
+ enum TargetSdk {
+ SDK_UNKNOWN = 0;
+ SDK_PRE_S = 1;
+ SDK_S = 2;
+ SDK_T = 3;
+ }
+ // indicates the target sdk level for this app.
+ optional TargetSdk target_sdk = 11;
+
+ // indicates if the current device is a low ram device.
+ optional bool low_mem_device = 12;
+}
+
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7069e716087d..dd69fa0bebb2 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2303,6 +2303,7 @@
<java-symbol type="drawable" name="scrubber_control_disabled_holo" />
<java-symbol type="drawable" name="scrubber_control_selector_holo" />
<java-symbol type="drawable" name="scrubber_progress_horizontal_holo_dark" />
+ <java-symbol type="drawable" name="progress_small_material" />
<java-symbol type="string" name="chooseUsbActivity" />
<java-symbol type="string" name="ext_media_badremoval_notification_message" />
<java-symbol type="string" name="ext_media_badremoval_notification_title" />
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index 3e261a7113ac..50639be57f22 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -157,7 +157,7 @@ public class ObjectPoolTests {
.setPendingResults(resultInfoList()).setPendingNewIntents(referrerIntentList())
.setIsForward(true).setAssistToken(assistToken)
.setShareableActivityToken(shareableActivityToken)
- .build();
+ .setTaskFragmentToken(new Binder()).build();
LaunchActivityItem emptyItem = new LaunchActivityItemBuilder().build();
LaunchActivityItem item = itemSupplier.get();
diff --git a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
index 1467fed898c4..26d9628ba55b 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
@@ -110,6 +110,7 @@ class TestUtils {
private IBinder mAssistToken;
private IBinder mShareableActivityToken;
private boolean mLaunchedFromBubble;
+ private IBinder mTaskFragmentToken;
LaunchActivityItemBuilder setIntent(Intent intent) {
mIntent = intent;
@@ -206,13 +207,18 @@ class TestUtils {
return this;
}
+ LaunchActivityItemBuilder setTaskFragmentToken(IBinder taskFragmentToken) {
+ mTaskFragmentToken = taskFragmentToken;
+ return this;
+ }
+
LaunchActivityItem build() {
return LaunchActivityItem.obtain(mIntent, mIdent, mInfo,
mCurConfig, mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor,
mProcState, mState, mPersistentState, mPendingResults, mPendingNewIntents,
mActivityOptions, mIsForward, mProfilerInfo, mAssistToken,
null /* activityClientController */, mShareableActivityToken,
- mLaunchedFromBubble);
+ mLaunchedFromBubble, mTaskFragmentToken);
}
}
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index beadc4464516..8276d10be4cd 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -203,6 +203,7 @@ public class TransactionParcelTests {
.setPendingResults(resultInfoList()).setActivityOptions(ActivityOptions.makeBasic())
.setPendingNewIntents(referrerIntentList()).setIsForward(true)
.setAssistToken(new Binder()).setShareableActivityToken(new Binder())
+ .setTaskFragmentToken(new Binder())
.build();
writeAndPrepareForReading(item);
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index b006a1681a99..8d3751e6ad6c 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -345,7 +345,7 @@ public class ActivityThreadClientTest {
null /* pendingResults */, null /* pendingNewIntents */,
null /* activityOptions */, true /* isForward */, null /* profilerInfo */,
mThread /* client */, null /* asssitToken */, null /* shareableActivityToken */,
- false /* launchedFromBubble */);
+ false /* launchedFromBubble */, null /* taskfragmentToken */);
}
@Override
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 449fb0242d09..58a2073981bf 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -273,6 +273,7 @@ applications that come with the platform
<permission name="android.permission.LOCAL_MAC_ADDRESS"/>
<permission name="android.permission.MANAGE_ACCESSIBILITY"/>
<permission name="android.permission.MANAGE_DEVICE_ADMINS"/>
+ <permission name="android.permission.ACCESS_FPS_COUNTER"/>
<permission name="android.permission.MANAGE_GAME_MODE"/>
<permission name="android.permission.MANAGE_GAME_ACTIVITY" />
<permission name="android.permission.MANAGE_LOW_POWER_STANDBY" />
diff --git a/framework-jarjar-rules.txt b/framework-jarjar-rules.txt
index be21f4e87101..03b268d87d01 100644
--- a/framework-jarjar-rules.txt
+++ b/framework-jarjar-rules.txt
@@ -5,3 +5,6 @@ rule android.hidl.** android.internal.hidl.@1
# Framework-specific renames.
rule android.net.wifi.WifiAnnotations* android.internal.wifi.WifiAnnotations@1
rule com.android.server.vcn.util.** com.android.server.vcn.repackaged.util.@1
+
+# for modules-utils-build dependency
+rule com.android.modules.utils.build.** android.internal.modules.utils.build.@1
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index c76fa9658c67..01f5feb9b13e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -615,14 +615,22 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return null;
}
+ private void updateCallbackIfNecessary() {
+ updateCallbackIfNecessary(true /* deferCallbackUntilAllActivitiesCreated */);
+ }
+
/**
* Notifies listeners about changes to split states if necessary.
+ *
+ * @param deferCallbackUntilAllActivitiesCreated boolean to indicate whether the split info
+ * callback should be deferred until all the
+ * organized activities have been created.
*/
- private void updateCallbackIfNecessary() {
+ private void updateCallbackIfNecessary(boolean deferCallbackUntilAllActivitiesCreated) {
if (mEmbeddingCallback == null) {
return;
}
- if (!allActivitiesCreated()) {
+ if (deferCallbackUntilAllActivitiesCreated && !allActivitiesCreated()) {
return;
}
List<SplitInfo> currentSplitStates = getActiveSplitStates();
@@ -838,6 +846,36 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter {
@Override
+ public void onActivityPreCreated(Activity activity, Bundle savedInstanceState) {
+ final IBinder activityToken = activity.getActivityToken();
+ final IBinder initialTaskFragmentToken = ActivityThread.currentActivityThread()
+ .getActivityClient(activityToken).mInitialTaskFragmentToken;
+ // If the activity is not embedded, then it will not have an initial task fragment token
+ // so no further action is needed.
+ if (initialTaskFragmentToken == null) {
+ return;
+ }
+ for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
+ final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i)
+ .mContainers;
+ for (int j = containers.size() - 1; j >= 0; j--) {
+ final TaskFragmentContainer container = containers.get(j);
+ if (!container.hasActivity(activityToken)
+ && container.getTaskFragmentToken().equals(initialTaskFragmentToken)) {
+ // The onTaskFragmentInfoChanged callback containing this activity has not
+ // reached the client yet, so add the activity to the pending appeared
+ // activities and send a split info callback to the client before
+ // {@link Activity#onCreate} is called.
+ container.addPendingAppearedActivity(activity);
+ updateCallbackIfNecessary(
+ false /* deferCallbackUntilAllActivitiesCreated */);
+ return;
+ }
+ }
+ }
+ }
+
+ @Override
public void onActivityPostCreated(Activity activity, Bundle savedInstanceState) {
// Calling after Activity#onCreate is complete to allow the app launch something
// first. In case of a configured placeholder activity we want to make sure
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index e49af41d4eac..9a12669f078a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -120,7 +120,7 @@ class TaskFragmentContainer {
}
ActivityStack toActivityStack() {
- return new ActivityStack(collectActivities(), mInfo.getRunningActivityCount() == 0);
+ return new ActivityStack(collectActivities(), isEmpty());
}
void addPendingAppearedActivity(@NonNull Activity pendingAppearedActivity) {
diff --git a/libs/WindowManager/Jetpack/tests/OWNERS b/libs/WindowManager/Jetpack/tests/OWNERS
index f2c3388023be..ac522b2dde10 100644
--- a/libs/WindowManager/Jetpack/tests/OWNERS
+++ b/libs/WindowManager/Jetpack/tests/OWNERS
@@ -1,4 +1,4 @@
-# Bug component: 909476
+# Bug component: 1157642
# includes OWNERS from parent directories
charlesccchen@google.com
diegovela@google.com
diff --git a/libs/WindowManager/Shell/res/drawable/home_icon.xml b/libs/WindowManager/Shell/res/drawable/home_icon.xml
new file mode 100644
index 000000000000..1669d0167e4b
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/home_icon.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+ android:gravity="center">
+ <item android:gravity="center">
+ <shape
+ android:shape="oval">
+ <stroke
+ android:color="@color/tv_pip_edu_text_home_icon"
+ android:width="1sp" />
+ <solid android:color="@android:color/transparent" />
+ <size
+ android:width="@dimen/pip_menu_edu_text_home_icon_outline"
+ android:height="@dimen/pip_menu_edu_text_home_icon_outline"/>
+ </shape>
+ </item>
+ <item
+ android:width="@dimen/pip_menu_edu_text_home_icon"
+ android:height="@dimen/pip_menu_edu_text_home_icon"
+ android:gravity="center">
+ <vector
+ android:width="24sp"
+ android:height="24sp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@color/tv_pip_edu_text_home_icon"
+ android:pathData="M12,3L4,9v12h5v-7h6v7h5V9z"/>
+ </vector>
+ </item>
+</layer-list>
diff --git a/libs/WindowManager/Shell/res/drawable/tv_pip_menu_background.xml b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_background.xml
new file mode 100644
index 000000000000..0c627921573c
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_background.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners android:radius="@dimen/pip_menu_background_corner_radius" />
+ <solid android:color="@color/tv_pip_menu_background"/>
+ <stroke android:width="@dimen/pip_menu_border_width"
+ android:color="@color/tv_pip_menu_background"/>
+</shape>
diff --git a/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
index 9bc03112b118..846fdb3e8a58 100644
--- a/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
+++ b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
@@ -14,9 +14,20 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <corners android:radius="@dimen/pip_menu_border_radius" />
- <stroke android:width="@dimen/pip_menu_border_width"
- android:color="@color/tv_pip_menu_focus_border" />
-</shape> \ No newline at end of file
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:exitFadeDuration="@integer/pip_menu_fade_animation_duration">
+ <item android:state_activated="true">
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/pip_menu_border_corner_radius" />
+ <stroke android:width="@dimen/pip_menu_border_width"
+ android:color="@color/tv_pip_menu_focus_border" />
+ </shape>
+ </item>
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/pip_menu_border_corner_radius" />
+ <stroke android:width="@dimen/pip_menu_border_width"
+ android:color="@color/tv_pip_menu_background"/>
+ </shape>
+ </item>
+</selector>
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
index b826d03bf765..dbd5a9b370ab 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
@@ -16,26 +16,41 @@
-->
<!-- Layout for TvPipMenuView -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/tv_pip_menu"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:id="@+id/tv_pip_menu"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center|top">
+
+ <View
+ android:id="@+id/tv_pip"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginTop="@dimen/pip_menu_outer_space"
+ android:layout_marginStart="@dimen/pip_menu_outer_space"
+ android:layout_marginEnd="@dimen/pip_menu_outer_space"/>
<ScrollView
android:id="@+id/tv_pip_menu_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
+ android:layout_alignTop="@+id/tv_pip"
+ android:layout_alignStart="@+id/tv_pip"
+ android:layout_alignEnd="@+id/tv_pip"
+ android:layout_alignBottom="@+id/tv_pip"
android:scrollbars="none"
- android:layout_margin="@dimen/pip_menu_outer_space"
android:visibility="gone"/>
<HorizontalScrollView
android:id="@+id/tv_pip_menu_horizontal_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:layout_alignTop="@+id/tv_pip"
+ android:layout_alignStart="@+id/tv_pip"
+ android:layout_alignEnd="@+id/tv_pip"
+ android:layout_alignBottom="@+id/tv_pip"
android:gravity="center_vertical"
- android:scrollbars="none"
- android:layout_margin="@dimen/pip_menu_outer_space">
+ android:scrollbars="none">
<LinearLayout
android:id="@+id/tv_pip_menu_action_buttons"
@@ -89,10 +104,44 @@
</HorizontalScrollView>
<View
+ android:id="@+id/tv_pip_border"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginTop="@dimen/pip_menu_outer_space_frame"
+ android:layout_marginStart="@dimen/pip_menu_outer_space_frame"
+ android:layout_marginEnd="@dimen/pip_menu_outer_space_frame"
+ android:background="@drawable/tv_pip_menu_border"/>
+
+ <FrameLayout
+ android:id="@+id/tv_pip_menu_edu_text_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_below="@+id/tv_pip"
+ android:layout_alignBottom="@+id/tv_pip_menu_frame"
+ android:layout_alignStart="@+id/tv_pip"
+ android:layout_alignEnd="@+id/tv_pip"
+ android:background="@color/tv_pip_menu_background"
+ android:clipChildren="true">
+
+ <TextView
+ android:id="@+id/tv_pip_menu_edu_text"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/pip_menu_edu_text_view_height"
+ android:layout_gravity="bottom|center"
+ android:gravity="center"
+ android:paddingBottom="@dimen/pip_menu_border_width"
+ android:text="@string/pip_edu_text"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:marqueeRepeatLimit="1"
+ android:scrollHorizontally="true"
+ android:textAppearance="@style/TvPipEduText"/>
+ </FrameLayout>
+
+ <View
android:id="@+id/tv_pip_menu_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:alpha="0"
android:layout_margin="@dimen/pip_menu_outer_space_frame"
android:background="@drawable/tv_pip_menu_border"/>
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu_background.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu_background.xml
new file mode 100644
index 000000000000..5af40200d240
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu_background.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<!-- Layout for the back surface of the PiP menu -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="@dimen/pip_menu_outer_space_frame"
+ android:background="@drawable/tv_pip_menu_background"
+ android:elevation="@dimen/pip_menu_elevation"/>
+</FrameLayout>
+
diff --git a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
index 558ec51752b9..776b18ecc01b 100644
--- a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
+++ b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
@@ -22,7 +22,12 @@
<dimen name="pip_menu_button_margin">4dp</dimen>
<dimen name="pip_menu_button_wrapper_margin">26dp</dimen>
<dimen name="pip_menu_border_width">4dp</dimen>
- <dimen name="pip_menu_border_radius">4dp</dimen>
+ <integer name="pip_menu_fade_animation_duration">500</integer>
+ <!-- The pip menu front border corner radius is 2dp smaller than
+ the background corner radius to hide the background from
+ showing through. -->
+ <dimen name="pip_menu_border_corner_radius">4dp</dimen>
+ <dimen name="pip_menu_background_corner_radius">6dp</dimen>
<dimen name="pip_menu_outer_space">24dp</dimen>
<!-- outer space minus border width -->
@@ -30,5 +35,14 @@
<dimen name="pip_menu_arrow_size">24dp</dimen>
<dimen name="pip_menu_arrow_elevation">5dp</dimen>
+
+ <dimen name="pip_menu_elevation">1dp</dimen>
+
+ <dimen name="pip_menu_edu_text_view_height">24dp</dimen>
+ <dimen name="pip_menu_edu_text_home_icon">9sp</dimen>
+ <dimen name="pip_menu_edu_text_home_icon_outline">14sp</dimen>
+ <integer name="pip_edu_text_show_duration_ms">10500</integer>
+ <integer name="pip_edu_text_window_exit_animation_duration_ms">1000</integer>
+ <integer name="pip_edu_text_view_exit_animation_duration_ms">300</integer>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/colors_tv.xml b/libs/WindowManager/Shell/res/values/colors_tv.xml
index 64b146ec3a83..fa90fe36b545 100644
--- a/libs/WindowManager/Shell/res/values/colors_tv.xml
+++ b/libs/WindowManager/Shell/res/values/colors_tv.xml
@@ -23,4 +23,8 @@
<color name="tv_pip_menu_icon_bg_focused">#E8EAED</color>
<color name="tv_pip_menu_icon_bg_unfocused">#990E0E0F</color>
<color name="tv_pip_menu_focus_border">#E8EAED</color>
-</resources> \ No newline at end of file
+ <color name="tv_pip_menu_background">#1E232C</color>
+
+ <color name="tv_pip_edu_text">#99D2E3FC</color>
+ <color name="tv_pip_edu_text_home_icon">#D2E3FC</color>
+</resources>
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index 7733201d2465..19f7c3ef4364 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -47,4 +47,13 @@
<item name="android:layout_width">96dp</item>
<item name="android:layout_height">48dp</item>
</style>
+
+ <style name="TvPipEduText">
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+ <item name="android:textAllCaps">true</item>
+ <item name="android:textSize">10sp</item>
+ <item name="android:lineSpacingExtra">4sp</item>
+ <item name="android:lineHeight">16sp</item>
+ <item name="android:textColor">@color/tv_pip_edu_text</item>
+ </style>
</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 73f393140cbc..7f9ecca20b48 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -63,6 +63,7 @@ import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.phone.PhonePipMenuController;
import com.android.wm.shell.pip.phone.PipAppOpsListener;
import com.android.wm.shell.pip.phone.PipController;
+import com.android.wm.shell.pip.phone.PipKeepClearAlgorithm;
import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.recents.RecentTasksController;
@@ -207,7 +208,8 @@ public class WMShellModule {
@Provides
static Optional<Pip> providePip(Context context, DisplayController displayController,
PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm,
- PipBoundsState pipBoundsState, PipMediaController pipMediaController,
+ PipKeepClearAlgorithm pipKeepClearAlgorithm, PipBoundsState pipBoundsState,
+ PipMotionHelper pipMotionHelper, PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController,
WindowManagerShellWrapper windowManagerShellWrapper,
@@ -215,9 +217,11 @@ public class WMShellModule {
Optional<OneHandedController> oneHandedController,
@ShellMainThread ShellExecutor mainExecutor) {
return Optional.ofNullable(PipController.create(context, displayController,
- pipAppOpsListener, pipBoundsAlgorithm, pipBoundsState, pipMediaController,
- phonePipMenuController, pipTaskOrganizer, pipTouchHandler, pipTransitionController,
- windowManagerShellWrapper, taskStackListener, oneHandedController, mainExecutor));
+ pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState,
+ pipMotionHelper,
+ pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTouchHandler,
+ pipTransitionController, windowManagerShellWrapper, taskStackListener,
+ oneHandedController, mainExecutor));
}
@WMSingleton
@@ -234,6 +238,12 @@ public class WMShellModule {
@WMSingleton
@Provides
+ static PipKeepClearAlgorithm providePipKeepClearAlgorithm() {
+ return new PipKeepClearAlgorithm();
+ }
+
+ @WMSingleton
+ @Provides
static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context,
PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm) {
return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 623ef05ec7e2..175a2445f28d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -107,7 +107,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb
private PipAppOpsListener mAppOpsListener;
private PipMediaController mMediaController;
private PipBoundsAlgorithm mPipBoundsAlgorithm;
+ private PipKeepClearAlgorithm mPipKeepClearAlgorithm;
private PipBoundsState mPipBoundsState;
+ private PipMotionHelper mPipMotionHelper;
private PipTouchHandler mTouchHandler;
private PipTransitionController mPipTransitionController;
private TaskStackListenerImpl mTaskStackListener;
@@ -241,6 +243,10 @@ public class PipController implements PipTransitionController.PipTransitionCallb
Set<Rect> unrestricted) {
if (mPipBoundsState.getDisplayId() == displayId) {
mPipBoundsState.setKeepClearAreas(restricted, unrestricted);
+ mPipMotionHelper.moveToBounds(mPipKeepClearAlgorithm.adjust(
+ mPipBoundsState.getBounds(),
+ mPipBoundsState.getRestrictedKeepClearAreas(),
+ mPipBoundsState.getUnrestrictedKeepClearAreas()));
}
}
};
@@ -293,7 +299,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
@Nullable
public static Pip create(Context context, DisplayController displayController,
PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm,
- PipBoundsState pipBoundsState, PipMediaController pipMediaController,
+ PipKeepClearAlgorithm pipKeepClearAlgorithm, PipBoundsState pipBoundsState,
+ PipMotionHelper pipMotionHelper, PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController,
WindowManagerShellWrapper windowManagerShellWrapper,
@@ -307,9 +314,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
return new PipController(context, displayController, pipAppOpsListener, pipBoundsAlgorithm,
- pipBoundsState, pipMediaController, phonePipMenuController, pipTaskOrganizer,
- pipTouchHandler, pipTransitionController, windowManagerShellWrapper,
- taskStackListener, oneHandedController, mainExecutor)
+ pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper, pipMediaController,
+ phonePipMenuController, pipTaskOrganizer, pipTouchHandler, pipTransitionController,
+ windowManagerShellWrapper, taskStackListener, oneHandedController, mainExecutor)
.mImpl;
}
@@ -317,7 +324,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb
DisplayController displayController,
PipAppOpsListener pipAppOpsListener,
PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipKeepClearAlgorithm pipKeepClearAlgorithm,
@NonNull PipBoundsState pipBoundsState,
+ PipMotionHelper pipMotionHelper,
PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController,
PipTaskOrganizer pipTaskOrganizer,
@@ -339,7 +348,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mWindowManagerShellWrapper = windowManagerShellWrapper;
mDisplayController = displayController;
mPipBoundsAlgorithm = pipBoundsAlgorithm;
+ mPipKeepClearAlgorithm = pipKeepClearAlgorithm;
mPipBoundsState = pipBoundsState;
+ mPipMotionHelper = pipMotionHelper;
mPipTaskOrganizer = pipTaskOrganizer;
mMainExecutor = mainExecutor;
mMediaController = pipMediaController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java
new file mode 100644
index 000000000000..a83258f9063b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java
@@ -0,0 +1,44 @@
+/*
+ * 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.wm.shell.pip.phone;
+
+import android.graphics.Rect;
+
+import java.util.Set;
+
+/**
+ * Calculates the adjusted position that does not occlude keep clear areas.
+ */
+public class PipKeepClearAlgorithm {
+
+ /** Returns a new {@code Rect} that does not occlude the provided keep clear areas. */
+ public Rect adjust(Rect defaultBounds, Set<Rect> restrictedKeepClearAreas,
+ Set<Rect> unrestrictedKeepClearAreas) {
+ if (restrictedKeepClearAreas.isEmpty()) {
+ return defaultBounds;
+ }
+ // TODO(b/183746978): implement the adjustment algorithm
+ // naively check if areas intersect, an if so move PiP upwards
+ Rect outBounds = new Rect(defaultBounds);
+ for (Rect r : restrictedKeepClearAreas) {
+ if (r.intersect(outBounds)) {
+ outBounds.offset(0, r.top - outBounds.bottom);
+ }
+ }
+ return outBounds;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/CenteredImageSpan.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/CenteredImageSpan.java
new file mode 100644
index 000000000000..6efdd57bdc48
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/CenteredImageSpan.java
@@ -0,0 +1,76 @@
+/*
+ * 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.wm.shell.pip.tv;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.text.style.ImageSpan;
+
+/** An ImageSpan for a Drawable that is centered vertically in the line. */
+public class CenteredImageSpan extends ImageSpan {
+
+ private Drawable mDrawable;
+
+ public CenteredImageSpan(Drawable drawable) {
+ super(drawable);
+ }
+
+ @Override
+ public int getSize(
+ Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fontMetrics) {
+ final Drawable drawable = getCachedDrawable();
+ final Rect rect = drawable.getBounds();
+
+ if (fontMetrics != null) {
+ Paint.FontMetricsInt paintFontMetrics = paint.getFontMetricsInt();
+ fontMetrics.ascent = paintFontMetrics.ascent;
+ fontMetrics.descent = paintFontMetrics.descent;
+ fontMetrics.top = paintFontMetrics.top;
+ fontMetrics.bottom = paintFontMetrics.bottom;
+ }
+
+ return rect.right;
+ }
+
+ @Override
+ public void draw(
+ Canvas canvas,
+ CharSequence text,
+ int start,
+ int end,
+ float x,
+ int top,
+ int y,
+ int bottom,
+ Paint paint) {
+ final Drawable drawable = getCachedDrawable();
+ canvas.save();
+ final int transY = (bottom - drawable.getBounds().bottom) / 2;
+ canvas.translate(x, transY);
+ drawable.draw(canvas);
+ canvas.restore();
+ }
+
+ private Drawable getCachedDrawable() {
+ if (mDrawable == null) {
+ mDrawable = getDrawable();
+ }
+ return mDrawable;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
index 1aefd77419aa..21d5d401835d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
@@ -27,6 +27,7 @@ import static com.android.wm.shell.pip.tv.TvPipBoundsState.ORIENTATION_VERTICAL;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Insets;
import android.graphics.Rect;
import android.os.SystemClock;
import android.util.ArraySet;
@@ -150,6 +151,10 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
mKeepClearAlgorithm.setScreenSize(screenSize);
mKeepClearAlgorithm.setMovementBounds(insetBounds);
mKeepClearAlgorithm.setStashOffset(mTvPipBoundsState.getStashOffset());
+ mKeepClearAlgorithm.setPipPermanentDecorInsets(
+ mTvPipBoundsState.getPipMenuPermanentDecorInsets());
+ mKeepClearAlgorithm.setPipTemporaryDecorInsets(
+ mTvPipBoundsState.getPipMenuTemporaryDecorInsets());
final Placement placement = mKeepClearAlgorithm.calculatePipPosition(
pipSize,
@@ -340,6 +345,7 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
final DisplayLayout displayLayout = mTvPipBoundsState.getDisplayLayout();
final float expandedRatio =
mTvPipBoundsState.getDesiredTvExpandedAspectRatio(); // width / height
+ final Insets pipDecorations = mTvPipBoundsState.getPipMenuPermanentDecorInsets();
final Size expandedSize;
if (expandedRatio == 0) {
@@ -352,7 +358,8 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) {
expandedSize = mTvPipBoundsState.getTvExpandedSize();
} else {
- int maxHeight = displayLayout.height() - (2 * mScreenEdgeInsets.y);
+ int maxHeight = displayLayout.height() - (2 * mScreenEdgeInsets.y)
+ - pipDecorations.top - pipDecorations.bottom;
float aspectRatioHeight = mFixedExpandedWidthInPx / expandedRatio;
if (maxHeight > aspectRatioHeight) {
@@ -374,7 +381,8 @@ public class TvPipBoundsAlgorithm extends PipBoundsAlgorithm {
if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_VERTICAL) {
expandedSize = mTvPipBoundsState.getTvExpandedSize();
} else {
- int maxWidth = displayLayout.width() - (2 * mScreenEdgeInsets.x);
+ int maxWidth = displayLayout.width() - (2 * mScreenEdgeInsets.x)
+ - pipDecorations.left - pipDecorations.right;
float aspectRatioWidth = mFixedExpandedHeightInPx * expandedRatio;
if (maxWidth > aspectRatioWidth) {
if (DEBUG) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
index 986554853034..ea074993bae1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
@@ -24,6 +24,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
+import android.graphics.Insets;
import android.util.Size;
import android.view.Gravity;
@@ -60,7 +61,8 @@ public class TvPipBoundsState extends PipBoundsState {
private @Orientation int mTvFixedPipOrientation;
private int mTvPipGravity;
private @Nullable Size mTvExpandedSize;
-
+ private @NonNull Insets mPipMenuPermanentDecorInsets = Insets.NONE;
+ private @NonNull Insets mPipMenuTemporaryDecorInsets = Insets.NONE;
public TvPipBoundsState(@NonNull Context context) {
super(context);
@@ -159,4 +161,19 @@ public class TvPipBoundsState extends PipBoundsState {
return mIsTvExpandedPipSupported;
}
+ public void setPipMenuPermanentDecorInsets(@NonNull Insets permanentInsets) {
+ mPipMenuPermanentDecorInsets = permanentInsets;
+ }
+
+ public @NonNull Insets getPipMenuPermanentDecorInsets() {
+ return mPipMenuPermanentDecorInsets;
+ }
+
+ public void setPipMenuTemporaryDecorInsets(@NonNull Insets temporaryDecorInsets) {
+ mPipMenuTemporaryDecorInsets = temporaryDecorInsets;
+ }
+
+ public @NonNull Insets getPipMenuTemporaryDecorInsets() {
+ return mPipMenuTemporaryDecorInsets;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 46b8e6098273..0e1f5a24f4a7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -115,6 +115,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
private int mPipForceCloseDelay;
private int mResizeAnimationDuration;
+ private int mEduTextWindowExitAnimationDurationMs;
public static Pip create(
Context context,
@@ -323,11 +324,15 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
}
}
+ private void updatePinnedStackBounds() {
+ updatePinnedStackBounds(mResizeAnimationDuration);
+ }
+
/**
* Update the PiP bounds based on the state of the PiP and keep clear areas.
* Animates to the current PiP bounds, and schedules unstashing the PiP if necessary.
*/
- private void updatePinnedStackBounds() {
+ private void updatePinnedStackBounds(int animationDuration) {
if (mState == STATE_NO_PIP) {
return;
}
@@ -353,23 +358,26 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
mUnstashRunnable = null;
}
if (!disallowStashing && placement.getUnstashDestinationBounds() != null) {
- mUnstashRunnable = () -> movePinnedStackTo(placement.getUnstashDestinationBounds());
+ mUnstashRunnable = () -> {
+ movePinnedStackTo(placement.getUnstashDestinationBounds(), animationDuration);
+ };
mMainHandler.postAtTime(mUnstashRunnable, placement.getUnstashTime());
}
}
/** Animates the PiP to the given bounds. */
private void movePinnedStackTo(Rect bounds) {
+ movePinnedStackTo(bounds, mResizeAnimationDuration);
+ }
+
+ /** Animates the PiP to the given bounds with the given animation duration. */
+ private void movePinnedStackTo(Rect bounds, int animationDuration) {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: movePinnedStack() - new pip bounds: %s", TAG, bounds.toShortString());
}
mPipTaskOrganizer.scheduleAnimateResizePip(bounds,
- mResizeAnimationDuration, rect -> {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: movePinnedStack() animation done", TAG);
- }
+ animationDuration, rect -> {
mTvPipMenuController.updateExpansionState();
});
}
@@ -408,6 +416,11 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
onPipDisappeared();
}
+ @Override
+ public void closeEduText() {
+ updatePinnedStackBounds(mEduTextWindowExitAnimationDurationMs);
+ }
+
private void registerSessionListenerForCurrentUser() {
mPipMediaController.registerSessionListenerForCurrentUser();
}
@@ -457,6 +470,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: onPipTransition_Started(), state=%s", TAG, stateToName(mState));
}
+ mTvPipMenuController.notifyPipAnimating(true);
}
@Override
@@ -465,6 +479,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: onPipTransition_Canceled(), state=%s", TAG, stateToName(mState));
}
+ mTvPipMenuController.notifyPipAnimating(false);
}
@Override
@@ -476,6 +491,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: onPipTransition_Finished(), state=%s", TAG, stateToName(mState));
}
+ mTvPipMenuController.notifyPipAnimating(false);
}
private void setState(@State int state) {
@@ -491,6 +507,8 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
final Resources res = mContext.getResources();
mResizeAnimationDuration = res.getInteger(R.integer.config_pipResizeAnimationDuration);
mPipForceCloseDelay = res.getInteger(R.integer.config_pipForceCloseDelay);
+ mEduTextWindowExitAnimationDurationMs =
+ res.getInteger(R.integer.pip_edu_text_window_exit_animation_duration_ms);
}
private void registerTaskStackListenerCallback(TaskStackListenerImpl taskStackListener) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
index 5ac7a7200494..9ede4433a978 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.pip.tv
+import android.graphics.Insets
import android.graphics.Point
import android.graphics.Rect
import android.util.Size
@@ -94,6 +95,13 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
private var lastAreasOverlappingUnstashPosition: Set<Rect> = emptySet()
private var lastStashTime: Long = Long.MIN_VALUE
+ /** Spaces around the PiP that we should leave space for when placing the PiP. Permanent PiP
+ * decorations are relevant for calculating intersecting keep clear areas */
+ private var pipPermanentDecorInsets = Insets.NONE
+ /** Spaces around the PiP that we should leave space for when placing the PiP. Temporary PiP
+ * decorations are not relevant for calculating intersecting keep clear areas */
+ private var pipTemporaryDecorInsets = Insets.NONE
+
/**
* Calculates the position the PiP should be placed at, taking into consideration the
* given keep clear areas.
@@ -120,20 +128,29 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
): Placement {
val transformedRestrictedAreas = transformAndFilterAreas(restrictedAreas)
val transformedUnrestrictedAreas = transformAndFilterAreas(unrestrictedAreas)
- val pipAnchorBounds = getNormalPipAnchorBounds(pipSize, transformedMovementBounds)
+ val pipSizeWithAllDecors = addDecors(pipSize)
+ val pipAnchorBoundsWithAllDecors =
+ getNormalPipAnchorBounds(pipSizeWithAllDecors, transformedMovementBounds)
+
+ val pipAnchorBoundsWithPermanentDecors = removeTemporaryDecors(pipAnchorBoundsWithAllDecors)
val result = calculatePipPositionTransformed(
- pipAnchorBounds,
+ pipAnchorBoundsWithPermanentDecors,
transformedRestrictedAreas,
transformedUnrestrictedAreas
)
- val screenSpaceBounds = fromTransformedSpace(result.bounds)
+ val pipBounds = removePermanentDecors(fromTransformedSpace(result.bounds))
+ val anchorBounds = removePermanentDecors(fromTransformedSpace(result.anchorBounds))
+ val unstashedDestBounds = result.unstashDestinationBounds?.let {
+ removePermanentDecors(fromTransformedSpace(it))
+ }
+
return Placement(
- screenSpaceBounds,
- fromTransformedSpace(result.anchorBounds),
- getStashType(screenSpaceBounds, movementBounds),
- result.unstashDestinationBounds?.let { fromTransformedSpace(it) },
+ pipBounds,
+ anchorBounds,
+ getStashType(pipBounds, movementBounds),
+ unstashedDestBounds,
result.unstashTime
)
}
@@ -447,6 +464,16 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
transformedMovementBounds = toTransformedSpace(movementBounds)
}
+ fun setPipPermanentDecorInsets(insets: Insets) {
+ if (pipPermanentDecorInsets == insets) return
+ pipPermanentDecorInsets = insets
+ }
+
+ fun setPipTemporaryDecorInsets(insets: Insets) {
+ if (pipTemporaryDecorInsets == insets) return
+ pipTemporaryDecorInsets = insets
+ }
+
/**
* @param open Whether this event marks the opening of an occupied segment
* @param pos The coordinate of this event
@@ -735,6 +762,35 @@ class TvPipKeepClearAlgorithm(private val clock: () -> Long) {
return horizontal && vertical
}
+ /**
+ * Adds space around [size] to leave space for decorations that will be drawn around the pip
+ */
+ private fun addDecors(size: Size): Size {
+ val bounds = Rect(0, 0, size.width, size.height)
+ bounds.inset(pipPermanentDecorInsets)
+ bounds.inset(pipTemporaryDecorInsets)
+
+ return Size(bounds.width(), bounds.height())
+ }
+
+ /**
+ * Removes the space that was reserved for permanent decorations around the pip
+ */
+ private fun removePermanentDecors(bounds: Rect): Rect {
+ val pipDecorReverseInsets = Insets.subtract(Insets.NONE, pipPermanentDecorInsets)
+ bounds.inset(pipDecorReverseInsets)
+ return bounds
+ }
+
+ /**
+ * Removes the space that was reserved for temporary decorations around the pip
+ */
+ private fun removeTemporaryDecors(bounds: Rect): Rect {
+ val pipDecorReverseInsets = Insets.subtract(Insets.NONE, pipTemporaryDecorInsets)
+ bounds.inset(pipDecorReverseInsets)
+ return bounds
+ }
+
private fun Rect.offsetCopy(dx: Int, dy: Int) = Rect(this).apply { offset(dx, dy) }
private fun Rect.intersectsY(other: Rect) = bottom >= other.top && top <= other.bottom
private fun Rect.intersectsX(other: Rect) = right >= other.left && left <= other.right
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index 35c34ac8315f..7b8dcf70cff0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -25,13 +25,17 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ParceledListSlice;
+import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Handler;
import android.os.RemoteException;
+import android.view.LayoutInflater;
import android.view.SurfaceControl;
import android.view.SyncRtSurfaceTransactionApplier;
+import android.view.View;
+import android.view.ViewRootImpl;
import android.view.WindowManagerGlobal;
import androidx.annotation.Nullable;
@@ -53,15 +57,20 @@ import java.util.Objects;
public class TvPipMenuController implements PipMenuController, TvPipMenuView.Listener {
private static final String TAG = "TvPipMenuController";
private static final boolean DEBUG = TvPipController.DEBUG;
+ private static final String BACKGROUND_WINDOW_TITLE = "PipBackgroundView";
private final Context mContext;
private final SystemWindows mSystemWindows;
private final TvPipBoundsState mTvPipBoundsState;
private final Handler mMainHandler;
+ private final int mPipMenuBorderWidth;
+ private final int mPipEduTextShowDurationMs;
+ private final int mPipEduTextHeight;
private Delegate mDelegate;
private SurfaceControl mLeash;
private TvPipMenuView mPipMenuView;
+ private View mPipBackgroundView;
// User can actively move the PiP via the DPAD.
private boolean mInMoveMode;
@@ -74,6 +83,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
private RemoteAction mCloseAction;
private SyncRtSurfaceTransactionApplier mApplier;
+ private SyncRtSurfaceTransactionApplier mBackgroundApplier;
RectF mTmpSourceRectF = new RectF();
RectF mTmpDestinationRectF = new RectF();
Matrix mMoveTransform = new Matrix();
@@ -91,6 +101,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
if (DEBUG) e.printStackTrace();
}
};
+ private final Runnable mCloseEduTextRunnable = this::closeEduText;
public TvPipMenuController(Context context, TvPipBoundsState tvPipBoundsState,
SystemWindows systemWindows, PipMediaController pipMediaController,
@@ -113,6 +124,13 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
mainHandler, Context.RECEIVER_EXPORTED);
pipMediaController.addActionListener(this::onMediaActionsChanged);
+
+ mPipEduTextShowDurationMs = context.getResources()
+ .getInteger(R.integer.pip_edu_text_show_duration_ms);
+ mPipEduTextHeight = context.getResources()
+ .getDimensionPixelSize(R.dimen.pip_menu_edu_text_view_height);
+ mPipMenuBorderWidth = context.getResources()
+ .getDimensionPixelSize(R.dimen.pip_menu_border_width);
}
void setDelegate(Delegate delegate) {
@@ -138,24 +156,63 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
mLeash = leash;
- attachPipMenuView();
+ attachPipMenu();
}
- private void attachPipMenuView() {
+ private void attachPipMenu() {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: attachPipMenuView()", TAG);
+ "%s: attachPipMenu()", TAG);
}
if (mPipMenuView != null) {
- detachPipMenuView();
+ detachPipMenu();
}
+ attachPipBackgroundView();
+ attachPipMenuView();
+
+ mTvPipBoundsState.setPipMenuPermanentDecorInsets(Insets.of(-mPipMenuBorderWidth,
+ -mPipMenuBorderWidth, -mPipMenuBorderWidth, -mPipMenuBorderWidth));
+ mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.of(0, 0, 0, -mPipEduTextHeight));
+ mMainHandler.postDelayed(mCloseEduTextRunnable, mPipEduTextShowDurationMs);
+ }
+
+ private void attachPipMenuView() {
mPipMenuView = new TvPipMenuView(mContext);
mPipMenuView.setListener(this);
- mSystemWindows.addView(mPipMenuView,
- getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
- 0, SHELL_ROOT_LAYER_PIP);
+ setUpViewSurfaceZOrder(mPipMenuView, 1);
+ addPipMenuViewToSystemWindows(mPipMenuView, MENU_WINDOW_TITLE);
+ }
+
+ private void attachPipBackgroundView() {
+ mPipBackgroundView = LayoutInflater.from(mContext)
+ .inflate(R.layout.tv_pip_menu_background, null);
+ setUpViewSurfaceZOrder(mPipBackgroundView, -1);
+ addPipMenuViewToSystemWindows(mPipBackgroundView, BACKGROUND_WINDOW_TITLE);
+ }
+
+ private void setUpViewSurfaceZOrder(View v, int zOrderRelativeToPip) {
+ v.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ v.getViewRootImpl().addSurfaceChangedCallback(
+ new PipMenuSurfaceChangedCallback(v, zOrderRelativeToPip));
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ }
+ });
+ }
+
+ private void addPipMenuViewToSystemWindows(View v, String title) {
+ mSystemWindows.addView(v, getPipMenuLayoutParams(title, 0 /* width */, 0 /* height */),
+ 0 /* displayId */, SHELL_ROOT_LAYER_PIP);
+ }
+
+ void notifyPipAnimating(boolean animating) {
+ mPipMenuView.setEduTextActive(!animating);
}
void showMovementMenuOnly() {
@@ -171,8 +228,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
@Override
public void showMenu() {
if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: showMenu()", TAG);
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMenu()", TAG);
}
mInMoveMode = false;
mCloseAfterExitMoveMenu = false;
@@ -183,27 +239,31 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
if (mPipMenuView == null) {
return;
}
- Rect menuBounds = getMenuBounds(mTvPipBoundsState.getBounds());
- mSystemWindows.updateViewLayout(mPipMenuView, getPipMenuLayoutParams(
- MENU_WINDOW_TITLE, menuBounds.width(), menuBounds.height()));
+ maybeCloseEduText();
maybeUpdateMenuViewActions();
updateExpansionState();
- SurfaceControl menuSurfaceControl = getSurfaceControl();
- if (menuSurfaceControl != null) {
- SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- t.setRelativeLayer(mPipMenuView.getWindowSurfaceControl(), mLeash, 1);
- t.setPosition(menuSurfaceControl, menuBounds.left, menuBounds.top);
- t.apply();
- }
grantPipMenuFocus(true);
if (mInMoveMode) {
mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
} else {
- mPipMenuView.showButtonMenu();
+ mPipMenuView.showButtonsMenu();
+ }
+ }
+
+ private void maybeCloseEduText() {
+ if (mMainHandler.hasCallbacks(mCloseEduTextRunnable)) {
+ mMainHandler.removeCallbacks(mCloseEduTextRunnable);
+ mCloseEduTextRunnable.run();
}
}
+ private void closeEduText() {
+ mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.NONE);
+ mPipMenuView.hideEduText();
+ mDelegate.closeEduText();
+ }
+
void updateGravity(int gravity) {
mPipMenuView.showMovementHints(gravity);
}
@@ -214,12 +274,8 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
mPipMenuView.setIsExpanded(mTvPipBoundsState.isTvPipExpanded());
}
- private Rect getMenuBounds(Rect pipBounds) {
- int extraSpaceInPx = mContext.getResources()
- .getDimensionPixelSize(R.dimen.pip_menu_outer_space);
- Rect menuBounds = new Rect(pipBounds);
- menuBounds.inset(-extraSpaceInPx, -extraSpaceInPx);
- return menuBounds;
+ private Rect calculateMenuSurfaceBounds(Rect pipBounds) {
+ return mPipMenuView.getPipMenuContainerBounds(pipBounds);
}
void closeMenu() {
@@ -227,11 +283,12 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: closeMenu()", TAG);
}
+
if (mPipMenuView == null) {
return;
}
- mPipMenuView.hideAll();
+ mPipMenuView.hideAllUserControls();
grantPipMenuFocus(false);
mDelegate.onMenuClosed();
}
@@ -266,7 +323,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
if (mInMoveMode) {
mInMoveMode = false;
- mPipMenuView.showButtonMenu();
+ mPipMenuView.showButtonsMenu();
return true;
}
return false;
@@ -287,7 +344,8 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
@Override
public void detach() {
closeMenu();
- detachPipMenuView();
+ mMainHandler.removeCallbacks(mCloseEduTextRunnable);
+ detachPipMenu();
mLeash = null;
}
@@ -346,20 +404,15 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
@Override
public boolean isMenuVisible() {
- boolean isVisible = mPipMenuView != null && mPipMenuView.isVisible();
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: isMenuVisible: %b", TAG, isVisible);
- }
- return isVisible;
+ return true;
}
/**
* Does an immediate window crop of the PiP menu.
*/
@Override
- public void resizePipMenu(@android.annotation.Nullable SurfaceControl pipLeash,
- @android.annotation.Nullable SurfaceControl.Transaction t,
+ public void resizePipMenu(@Nullable SurfaceControl pipLeash,
+ @Nullable SurfaceControl.Transaction t,
Rect destinationBounds) {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -373,24 +426,36 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
return;
}
- SurfaceControl surfaceControl = getSurfaceControl();
- SyncRtSurfaceTransactionApplier.SurfaceParams
- params = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(surfaceControl)
- .withWindowCrop(getMenuBounds(destinationBounds))
+ final Rect menuBounds = calculateMenuSurfaceBounds(destinationBounds);
+
+ final SurfaceControl frontSurface = getSurfaceControl(mPipMenuView);
+ final SyncRtSurfaceTransactionApplier.SurfaceParams frontParams =
+ new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(frontSurface)
+ .withWindowCrop(menuBounds)
+ .build();
+
+ final SurfaceControl backSurface = getSurfaceControl(mPipBackgroundView);
+ final SyncRtSurfaceTransactionApplier.SurfaceParams backParams =
+ new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(backSurface)
+ .withWindowCrop(menuBounds)
.build();
+
+ // TODO(b/226580399): switch to using SurfaceSyncer (see b/200284684) to synchronize the
+ // animations of the pip surface with the content of the front and back menu surfaces
+ mBackgroundApplier.scheduleApply(backParams);
if (pipLeash != null && t != null) {
- SyncRtSurfaceTransactionApplier.SurfaceParams
+ final SyncRtSurfaceTransactionApplier.SurfaceParams
pipParams = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(pipLeash)
.withMergeTransaction(t)
.build();
- mApplier.scheduleApply(params, pipParams);
+ mApplier.scheduleApply(frontParams, pipParams);
} else {
- mApplier.scheduleApply(params);
+ mApplier.scheduleApply(frontParams);
}
}
- private SurfaceControl getSurfaceControl() {
- return mSystemWindows.getViewSurface(mPipMenuView);
+ private SurfaceControl getSurfaceControl(View v) {
+ return mSystemWindows.getViewSurface(v);
}
@Override
@@ -412,44 +477,52 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
return;
}
- Rect menuDestBounds = getMenuBounds(pipDestBounds);
- Rect mTmpSourceBounds = new Rect();
+ final Rect menuDestBounds = calculateMenuSurfaceBounds(pipDestBounds);
+ final Rect tmpSourceBounds = new Rect();
// If there is no pip leash supplied, that means the PiP leash is already finalized
// resizing and the PiP menu is also resized. We then want to do a scale from the current
// new menu bounds.
if (pipLeash != null && transaction != null) {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: mTmpSourceBounds based on mPipMenuView.getBoundsOnScreen()", TAG);
+ "%s: tmpSourceBounds based on mPipMenuView.getBoundsOnScreen()", TAG);
}
- mPipMenuView.getBoundsOnScreen(mTmpSourceBounds);
+ mPipMenuView.getBoundsOnScreen(tmpSourceBounds);
} else {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: mTmpSourceBounds based on menu width and height", TAG);
+ "%s: tmpSourceBounds based on menu width and height", TAG);
}
- mTmpSourceBounds.set(0, 0, menuDestBounds.width(), menuDestBounds.height());
+ tmpSourceBounds.set(0, 0, menuDestBounds.width(), menuDestBounds.height());
}
- mTmpSourceRectF.set(mTmpSourceBounds);
+ mTmpSourceRectF.set(tmpSourceBounds);
mTmpDestinationRectF.set(menuDestBounds);
- mMoveTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
+ mMoveTransform.setTranslate(mTmpDestinationRectF.left, mTmpDestinationRectF.top);
+
+ final SurfaceControl frontSurface = getSurfaceControl(mPipMenuView);
+ final SyncRtSurfaceTransactionApplier.SurfaceParams frontParams =
+ new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(frontSurface)
+ .withMatrix(mMoveTransform)
+ .build();
- SurfaceControl surfaceControl = getSurfaceControl();
- SyncRtSurfaceTransactionApplier.SurfaceParams params =
- new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
- surfaceControl)
+ final SurfaceControl backSurface = getSurfaceControl(mPipBackgroundView);
+ final SyncRtSurfaceTransactionApplier.SurfaceParams backParams =
+ new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(backSurface)
.withMatrix(mMoveTransform)
.build();
+ // TODO(b/226580399): switch to using SurfaceSyncer (see b/200284684) to synchronize the
+ // animations of the pip surface with the content of the front and back menu surfaces
+ mBackgroundApplier.scheduleApply(backParams);
if (pipLeash != null && transaction != null) {
- SyncRtSurfaceTransactionApplier.SurfaceParams
- pipParams = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(pipLeash)
+ final SyncRtSurfaceTransactionApplier.SurfaceParams pipParams =
+ new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(pipLeash)
.withMergeTransaction(transaction)
.build();
- mApplier.scheduleApply(params, pipParams);
+ mApplier.scheduleApply(frontParams, pipParams);
} else {
- mApplier.scheduleApply(params);
+ mApplier.scheduleApply(frontParams);
}
if (mPipMenuView.getViewRootImpl() != null) {
@@ -470,29 +543,40 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
if (mApplier == null) {
mApplier = new SyncRtSurfaceTransactionApplier(mPipMenuView);
}
+ if (mBackgroundApplier == null) {
+ mBackgroundApplier = new SyncRtSurfaceTransactionApplier(mPipBackgroundView);
+ }
return true;
}
- private void detachPipMenuView() {
- if (mPipMenuView == null) {
- return;
+ private void detachPipMenu() {
+ if (mPipMenuView != null) {
+ mApplier = null;
+ mSystemWindows.removeView(mPipMenuView);
+ mPipMenuView = null;
}
- mApplier = null;
- mSystemWindows.removeView(mPipMenuView);
- mPipMenuView = null;
+ if (mPipBackgroundView != null) {
+ mBackgroundApplier = null;
+ mSystemWindows.removeView(mPipBackgroundView);
+ mPipBackgroundView = null;
+ }
}
@Override
public void updateMenuBounds(Rect destinationBounds) {
- Rect menuBounds = getMenuBounds(destinationBounds);
+ final Rect menuBounds = calculateMenuSurfaceBounds(destinationBounds);
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: updateMenuBounds: %s", TAG, menuBounds.toShortString());
}
+ mSystemWindows.updateViewLayout(mPipBackgroundView,
+ getPipMenuLayoutParams(BACKGROUND_WINDOW_TITLE, menuBounds.width(),
+ menuBounds.height()));
mSystemWindows.updateViewLayout(mPipMenuView,
getPipMenuLayoutParams(MENU_WINDOW_TITLE, menuBounds.width(),
menuBounds.height()));
+
if (mPipMenuView != null) {
mPipMenuView.updateLayout(destinationBounds);
}
@@ -538,6 +622,8 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
void onMenuClosed();
+ void closeEduText();
+
void closePip();
}
@@ -555,4 +641,30 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
"%s: Unable to update focus, %s", TAG, e);
}
}
+
+ private class PipMenuSurfaceChangedCallback implements ViewRootImpl.SurfaceChangedCallback {
+ private final View mView;
+ private final int mZOrder;
+
+ PipMenuSurfaceChangedCallback(View v, int zOrder) {
+ mView = v;
+ mZOrder = zOrder;
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceControl.Transaction t) {
+ final SurfaceControl sc = getSurfaceControl(mView);
+ if (sc != null) {
+ t.setRelativeLayer(sc, mLeash, mZOrder);
+ }
+ }
+
+ @Override
+ public void surfaceReplaced(SurfaceControl.Transaction t) {
+ }
+
+ @Override
+ public void surfaceDestroyed() {
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 9529d04fe185..5b0db8c86529 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -25,11 +25,17 @@ import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT;
import static android.view.KeyEvent.KEYCODE_DPAD_UP;
import static android.view.KeyEvent.KEYCODE_ENTER;
+import android.animation.ValueAnimator;
import android.app.PendingIntent;
import android.app.RemoteAction;
import android.content.Context;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.os.Handler;
+import android.text.Annotation;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.SpannedString;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.KeyEvent;
@@ -40,6 +46,7 @@ import android.view.ViewRootImpl;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
+import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -49,6 +56,7 @@ import com.android.wm.shell.R;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@@ -67,6 +75,15 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
private final LinearLayout mActionButtonsContainer;
private final View mMenuFrameView;
private final List<TvPipMenuActionButton> mAdditionalButtons = new ArrayList<>();
+ private final View mPipFrameView;
+ private final View mPipView;
+ private final TextView mEduTextView;
+ private final View mEduTextContainerView;
+ private final int mPipMenuOuterSpace;
+ private final int mPipMenuBorderWidth;
+ private final int mEduTextFadeExitAnimationDurationMs;
+ private final int mEduTextSlideExitAnimationDurationMs;
+ private int mEduTextHeight;
private final ImageView mArrowUp;
private final ImageView mArrowRight;
@@ -76,11 +93,13 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
private final ViewGroup mScrollView;
private final ViewGroup mHorizontalScrollView;
- private Rect mCurrentBounds;
+ private Rect mCurrentPipBounds;
private final TvPipMenuActionButton mExpandButton;
private final TvPipMenuActionButton mCloseButton;
+ private final int mPipMenuFadeAnimationDuration;
+
public TvPipMenuView(@NonNull Context context) {
this(context, null);
}
@@ -116,21 +135,86 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
mHorizontalScrollView = findViewById(R.id.tv_pip_menu_horizontal_scroll);
mMenuFrameView = findViewById(R.id.tv_pip_menu_frame);
+ mPipFrameView = findViewById(R.id.tv_pip_border);
+ mPipView = findViewById(R.id.tv_pip);
mArrowUp = findViewById(R.id.tv_pip_menu_arrow_up);
mArrowRight = findViewById(R.id.tv_pip_menu_arrow_right);
mArrowDown = findViewById(R.id.tv_pip_menu_arrow_down);
mArrowLeft = findViewById(R.id.tv_pip_menu_arrow_left);
+
+ mEduTextView = findViewById(R.id.tv_pip_menu_edu_text);
+ mEduTextContainerView = findViewById(R.id.tv_pip_menu_edu_text_container);
+
+ mPipMenuFadeAnimationDuration = context.getResources()
+ .getInteger(R.integer.pip_menu_fade_animation_duration);
+ mPipMenuOuterSpace = context.getResources()
+ .getDimensionPixelSize(R.dimen.pip_menu_outer_space);
+ mPipMenuBorderWidth = context.getResources()
+ .getDimensionPixelSize(R.dimen.pip_menu_border_width);
+ mEduTextHeight = context.getResources()
+ .getDimensionPixelSize(R.dimen.pip_menu_edu_text_view_height);
+ mEduTextFadeExitAnimationDurationMs = context.getResources()
+ .getInteger(R.integer.pip_edu_text_view_exit_animation_duration_ms);
+ mEduTextSlideExitAnimationDurationMs = context.getResources()
+ .getInteger(R.integer.pip_edu_text_window_exit_animation_duration_ms);
+
+ initEduText();
+ }
+
+ void initEduText() {
+ final SpannedString eduText = (SpannedString) getResources().getText(R.string.pip_edu_text);
+ final SpannableString spannableString = new SpannableString(eduText);
+ Arrays.stream(eduText.getSpans(0, eduText.length(), Annotation.class)).findFirst()
+ .ifPresent(annotation -> {
+ final Drawable icon =
+ getResources().getDrawable(R.drawable.home_icon, mContext.getTheme());
+ if (icon != null) {
+ icon.mutate();
+ icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
+ spannableString.setSpan(new CenteredImageSpan(icon),
+ eduText.getSpanStart(annotation),
+ eduText.getSpanEnd(annotation),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ });
+
+ mEduTextView.setText(spannableString);
}
- void updateLayout(Rect updatedBounds) {
+ void setEduTextActive(boolean active) {
+ mEduTextView.setSelected(active);
+ }
+
+ void hideEduText() {
+ final ValueAnimator heightAnimation = ValueAnimator.ofInt(mEduTextHeight, 0);
+ heightAnimation.setDuration(mEduTextSlideExitAnimationDurationMs);
+ heightAnimation.setInterpolator(TvPipInterpolators.BROWSE);
+ heightAnimation.addUpdateListener(animator -> {
+ mEduTextHeight = (int) animator.getAnimatedValue();
+ });
+ mEduTextView.animate()
+ .alpha(0f)
+ .setInterpolator(TvPipInterpolators.EXIT)
+ .setDuration(mEduTextFadeExitAnimationDurationMs)
+ .withEndAction(() -> {
+ mEduTextContainerView.setVisibility(GONE);
+ }).start();
+ heightAnimation.start();
+ }
+
+ void updateLayout(Rect updatedPipBounds) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: update menu layout: %s", TAG, updatedBounds.toShortString());
+ "%s: update menu layout: %s", TAG, updatedPipBounds.toShortString());
+
boolean previouslyVertical =
- mCurrentBounds != null && mCurrentBounds.height() > mCurrentBounds.width();
- boolean vertical = updatedBounds.height() > updatedBounds.width();
+ mCurrentPipBounds != null && mCurrentPipBounds.height() > mCurrentPipBounds.width();
+ boolean vertical = updatedPipBounds.height() > updatedPipBounds.width();
+
+ mCurrentPipBounds = updatedPipBounds;
+
+ updatePipFrameBounds();
- mCurrentBounds = updatedBounds;
if (previouslyVertical == vertical) {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -158,6 +242,38 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
mHorizontalScrollView.setVisibility(vertical ? GONE : VISIBLE);
}
+ Rect getPipMenuContainerBounds(Rect pipBounds) {
+ final Rect menuUiBounds = new Rect(pipBounds);
+ menuUiBounds.inset(-mPipMenuOuterSpace, -mPipMenuOuterSpace);
+ menuUiBounds.bottom += mEduTextHeight;
+ return menuUiBounds;
+ }
+
+ /**
+ * Update mPipFrameView's bounds according to the new pip window bounds. We can't
+ * make mPipFrameView match_parent, because the pip menu might contain other content around
+ * the pip window (e.g. edu text).
+ * TvPipMenuView needs to account for this so that it can draw a white border around the whole
+ * pip menu when it gains focus.
+ */
+ private void updatePipFrameBounds() {
+ final ViewGroup.LayoutParams pipFrameParams = mPipFrameView.getLayoutParams();
+ if (pipFrameParams != null) {
+ pipFrameParams.width = mCurrentPipBounds.width() + 2 * mPipMenuBorderWidth;
+ pipFrameParams.height = mCurrentPipBounds.height() + 2 * mPipMenuBorderWidth;
+ mPipFrameView.setLayoutParams(pipFrameParams);
+ }
+
+ final ViewGroup.LayoutParams pipViewParams = mPipView.getLayoutParams();
+ if (pipViewParams != null) {
+ pipViewParams.width = mCurrentPipBounds.width();
+ pipViewParams.height = mCurrentPipBounds.height();
+ mPipView.setLayoutParams(pipViewParams);
+ }
+
+
+ }
+
void setListener(@Nullable Listener listener) {
mListener = listener;
}
@@ -184,30 +300,32 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMoveMenu()", TAG);
}
- showMenuButtons(false);
+ showButtonsMenu(false);
showMovementHints(gravity);
- showMenuFrame(true);
+ setFrameHighlighted(true);
}
- void showButtonMenu() {
+ void showButtonsMenu() {
if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showButtonMenu()", TAG);
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: showButtonsMenu()", TAG);
}
- showMenuButtons(true);
+ showButtonsMenu(true);
hideMovementHints();
- showMenuFrame(true);
+ setFrameHighlighted(true);
}
/**
* Hides all menu views, including the menu frame.
*/
- void hideAll() {
+ void hideAllUserControls() {
if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: hideAll()", TAG);
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: hideAllUserControls()", TAG);
}
- showMenuButtons(false);
+ showButtonsMenu(false);
hideMovementHints();
- showMenuFrame(false);
+ setFrameHighlighted(false);
}
private void animateAlphaTo(float alpha, View view) {
@@ -217,7 +335,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
view.animate()
.alpha(alpha)
.setInterpolator(alpha == 0f ? TvPipInterpolators.EXIT : TvPipInterpolators.ENTER)
- .setDuration(500)
+ .setDuration(mPipMenuFadeAnimationDuration)
.withStartAction(() -> {
if (alpha != 0) {
view.setVisibility(VISIBLE);
@@ -230,15 +348,6 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
});
}
- boolean isVisible() {
- return mMenuFrameView.getAlpha() != 0f
- || mActionButtonsContainer.getAlpha() != 0f
- || mArrowUp.getAlpha() != 0f
- || mArrowRight.getAlpha() != 0f
- || mArrowDown.getAlpha() != 0f
- || mArrowLeft.getAlpha() != 0f;
- }
-
void setAdditionalActions(List<RemoteAction> actions, RemoteAction closeAction,
Handler mainHandler) {
if (DEBUG) {
@@ -423,18 +532,18 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
}
/**
- * Show or hide the pip user actions.
+ * Show or hide the pip buttons menu.
*/
- public void showMenuButtons(boolean show) {
+ public void showButtonsMenu(boolean show) {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: showMenuButtons: %b", TAG, show);
+ "%s: showUserActions: %b", TAG, show);
}
animateAlphaTo(show ? 1 : 0, mActionButtonsContainer);
}
- private void showMenuFrame(boolean show) {
- animateAlphaTo(show ? 1 : 0, mMenuFrameView);
+ private void setFrameHighlighted(boolean highlighted) {
+ mMenuFrameView.setActivated(highlighted);
}
interface Listener {
diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS
index a244d14b0e14..f4efc374ecc2 100644
--- a/libs/WindowManager/Shell/tests/OWNERS
+++ b/libs/WindowManager/Shell/tests/OWNERS
@@ -1,4 +1,4 @@
-# Bug component: 909476
+# Bug component: 1157642
# includes OWNERS from parent directories
natanieljr@google.com
pablogamito@google.com
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
index fb404b913465..684e5cad0e67 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
@@ -17,6 +17,8 @@
package com.android.wm.shell.flicker.bubble
import android.platform.test.annotations.Presubmit
+import android.view.WindowInsets
+import android.view.WindowManager
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
@@ -59,6 +61,16 @@ class LaunchBubbleFromLockScreen(testSpec: FlickerTestParameter) : BaseBubbleScr
}
}
transitions {
+ // Swipe & wait for the notification shade to expand so all can be seen
+ val wm = context.getSystemService(WindowManager::class.java)
+ val metricInsets = wm.getCurrentWindowMetrics().windowInsets
+ val insets = metricInsets.getInsetsIgnoringVisibility(
+ WindowInsets.Type.statusBars()
+ or WindowInsets.Type.displayCutout())
+ device.swipe(100, insets.top + 100, 100, device.getDisplayHeight() / 2, 4)
+ device.waitForIdle(2000)
+ instrumentation.uiAutomation.syncInputTransactions()
+
val notification = device.wait(Until.findObject(
By.text("BubbleChat")), FIND_OBJECT_TIMEOUT)
notification?.click() ?: error("Notification not found")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
index 3fe6f02eccf7..9a8c8942d2c9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.flicker.pip
+import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
@@ -84,7 +85,7 @@ class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransit
override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
/** {@inheritDoc} */
- @FlakyTest(bugId = 197726610)
+ @Presubmit
@Test
override fun pipLayerExpands() = super.pipLayerExpands()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index 5fc80db6c617..9f3fcea241e4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -111,7 +111,7 @@ class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition
/**
* Checks that the visible region of [pipApp] always expands during the animation
*/
- @Presubmit
+ @FlakyTest(bugId = 228012337)
@Test
fun pipLayerExpands() {
val layerName = pipApp.component.toLayerName()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index 6af01e24f58c..c1ee1a7cbb35 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -33,7 +33,6 @@ import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.wm.shell.flicker.helpers.FixedAppHelper
import org.junit.Assume
-import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -69,11 +68,6 @@ open class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testS
private val screenBoundsStart = WindowUtils.getDisplayBounds(testSpec.startRotation)
private val screenBoundsEnd = WindowUtils.getDisplayBounds(testSpec.endRotation)
- @Before
- open fun before() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- }
-
override val transition: FlickerBuilder.() -> Unit
get() = buildTransition(eachRun = false) {
setup {
@@ -135,15 +129,30 @@ open class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testS
/**
* Checks that [pipApp] layer is within [screenBoundsStart] at the start of the transition
*/
- @Presubmit
- @Test
- open fun pipLayerRotates_StartingBounds() {
+ private fun pipLayerRotates_StartingBounds_internal() {
testSpec.assertLayersStart {
visibleRegion(pipApp.component).coversAtMost(screenBoundsStart)
}
}
/**
+ * Checks that [pipApp] layer is within [screenBoundsStart] at the start of the transition
+ */
+ @Presubmit
+ @Test
+ fun pipLayerRotates_StartingBounds() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ pipLayerRotates_StartingBounds_internal()
+ }
+
+ @FlakyTest(bugId = 228024285)
+ @Test
+ fun pipLayerRotates_StartingBounds_ShellTransit() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ pipLayerRotates_StartingBounds_internal()
+ }
+
+ /**
* Checks that [pipApp] layer is within [screenBoundsEnd] at the end of the transition
*/
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
index 81403d08ff20..e40f2bc1ed5a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
@@ -47,7 +47,6 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group4
-@FlakyTest(bugId = 218604389)
open class SetRequestedOrientationWhilePinnedTest(
testSpec: FlickerTestParameter
) : PipTransition(testSpec) {
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java
index d743dffd3c9e..6cd93eff2803 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java
@@ -22,7 +22,6 @@ import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Person;
-import android.app.RemoteInput;
import android.content.Context;
import android.content.Intent;
import android.graphics.Point;
@@ -116,24 +115,20 @@ public class BubbleHelper {
private Notification.Builder getNotificationBuilder(int id) {
Person chatBot = new Person.Builder()
.setBot(true)
- .setName("BubbleBot")
+ .setName("BubbleChat")
.setImportant(true)
.build();
-
- RemoteInput remoteInput = new RemoteInput.Builder("key")
- .setLabel("Reply")
- .build();
-
String shortcutId = "BubbleChat";
return new Notification.Builder(mContext, CHANNEL_ID)
.setChannelId(CHANNEL_ID)
.setShortcutId(shortcutId)
+ .setContentTitle("BubbleChat")
.setContentIntent(PendingIntent.getActivity(mContext, 0,
new Intent(mContext, LaunchBubbleActivity.class),
PendingIntent.FLAG_UPDATE_CURRENT))
.setStyle(new Notification.MessagingStyle(chatBot)
- .setConversationTitle("Bubble Chat")
- .addMessage("Hello? This is bubble: " + id,
+ .setConversationTitle("BubbleChat")
+ .addMessage("BubbleChat",
SystemClock.currentThreadTimeMillis() - 300000, chatBot)
.addMessage("Is it me, " + id + ", you're looking for?",
SystemClock.currentThreadTimeMillis(), chatBot)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index aef298ed478a..deb955b30842 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -74,6 +74,7 @@ public class PipControllerTest extends ShellTestCase {
@Mock private PhonePipMenuController mMockPhonePipMenuController;
@Mock private PipAppOpsListener mMockPipAppOpsListener;
@Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm;
+ @Mock private PipKeepClearAlgorithm mMockPipKeepClearAlgorithm;
@Mock private PipSnapAlgorithm mMockPipSnapAlgorithm;
@Mock private PipMediaController mMockPipMediaController;
@Mock private PipTaskOrganizer mMockPipTaskOrganizer;
@@ -97,9 +98,10 @@ public class PipControllerTest extends ShellTestCase {
return null;
}).when(mMockExecutor).execute(any());
mPipController = new PipController(mContext, mMockDisplayController,
- mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipBoundsState,
- mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer,
- mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
+ mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
+ mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
+ mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTouchHandler,
+ mMockPipTransitionController, mMockWindowManagerShellWrapper,
mMockTaskStackListener, mMockOneHandedController, mMockExecutor);
when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm);
when(mMockPipTouchHandler.getMotionHelper()).thenReturn(mMockPipMotionHelper);
@@ -128,9 +130,10 @@ public class PipControllerTest extends ShellTestCase {
when(spyContext.getPackageManager()).thenReturn(mockPackageManager);
assertNull(PipController.create(spyContext, mMockDisplayController,
- mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipBoundsState,
- mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer,
- mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
+ mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
+ mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
+ mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTouchHandler,
+ mMockPipTransitionController, mMockWindowManagerShellWrapper,
mMockTaskStackListener, mMockOneHandedController, mMockExecutor));
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java
new file mode 100644
index 000000000000..f657b5e62d82
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.wm.shell.pip.phone;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import android.graphics.Rect;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+
+/**
+ * Unit tests against {@link PipKeepClearAlgorithm}.
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class PipKeepClearAlgorithmTest extends ShellTestCase {
+
+ private PipKeepClearAlgorithm mPipKeepClearAlgorithm;
+
+
+ @Before
+ public void setUp() throws Exception {
+ mPipKeepClearAlgorithm = new PipKeepClearAlgorithm();
+ }
+
+ @Test
+ public void adjust_withCollidingRestrictedKeepClearAreas_movesBounds() {
+ final Rect inBounds = new Rect(0, 0, 100, 100);
+ final Rect keepClearRect = new Rect(50, 50, 150, 150);
+
+ final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(keepClearRect),
+ Set.of());
+
+ assertFalse(outBounds.contains(keepClearRect));
+ }
+
+ @Test
+ public void adjust_withNonCollidingRestrictedKeepClearAreas_boundsDoNotChange() {
+ final Rect inBounds = new Rect(0, 0, 100, 100);
+ final Rect keepClearRect = new Rect(100, 100, 150, 150);
+
+ final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(keepClearRect),
+ Set.of());
+
+ assertEquals(inBounds, outBounds);
+ }
+
+ @Test
+ public void adjust_withCollidingUnrestrictedKeepClearAreas_boundsDoNotChange() {
+ // TODO(b/183746978): update this test to accommodate for the updated algorithm
+ final Rect inBounds = new Rect(0, 0, 100, 100);
+ final Rect keepClearRect = new Rect(50, 50, 150, 150);
+
+ final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(),
+ Set.of(keepClearRect));
+
+ assertEquals(inBounds, outBounds);
+ }
+
+ @Test
+ public void adjust_withNonCollidingUnrestrictedKeepClearAreas_boundsDoNotChange() {
+ final Rect inBounds = new Rect(0, 0, 100, 100);
+ final Rect keepClearRect = new Rect(100, 100, 150, 150);
+
+ final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(),
+ Set.of(keepClearRect));
+
+ assertEquals(inBounds, outBounds);
+ }
+}
diff --git a/location/java/android/location/LocationDeviceConfig.java b/location/java/android/location/LocationDeviceConfig.java
index c55eed9211f7..7d22681fc93e 100644
--- a/location/java/android/location/LocationDeviceConfig.java
+++ b/location/java/android/location/LocationDeviceConfig.java
@@ -24,6 +24,30 @@ package android.location;
public final class LocationDeviceConfig {
/**
+ * Package/tag combinations that are allowlisted for ignoring location settings (may retrieve
+ * location even when user location settings are off), for advanced driver-assistance systems
+ * only.
+ *
+ * <p>Package/tag combinations are separated by commas (","), and with in each combination is a
+ * package name followed by 0 or more attribution tags, separated by semicolons (";"). If a
+ * package is followed by 0 attribution tags, this is interpreted the same as the wildcard
+ * value. There are two special interpreted values for attribution tags, the wildcard value
+ * ("*") which represents all attribution tags, and the null value ("null"), which is converted
+ * to the null string (since attribution tags may be null). This format implies that attribution
+ * tags which should be on this list may not contain semicolons.
+ *
+ * <p>Examples of valid entries:
+ *
+ * <ul>
+ * <li>android
+ * <li>android;*
+ * <li>android;*,com.example.app;null;my_attr
+ * <li>android;*,com.example.app;null;my_attr,com.example.otherapp;my_attr
+ * </ul>
+ */
+ public static final String ADAS_SETTINGS_ALLOWLIST = "adas_settings_allowlist";
+
+ /**
* Package/tag combinations that are allowedlisted for ignoring location settings (may retrieve
* location even when user location settings are off, and may ignore throttling, etc), for
* emergency purposes only.
@@ -39,10 +63,10 @@ public final class LocationDeviceConfig {
* <p>Examples of valid entries:
*
* <ul>
- * <li>android</li>
- * <li>android;*</li>
- * <li>android;*,com.example.app;null;my_attr</li>
- * <li>android;*,com.example.app;null;my_attr,com.example.otherapp;my_attr</li>
+ * <li>android
+ * <li>android;*
+ * <li>android;*,com.example.app;null;my_attr
+ * <li>android;*,com.example.app;null;my_attr,com.example.otherapp;my_attr
* </ul>
*/
public static final String IGNORE_SETTINGS_ALLOWLIST = "ignore_settings_allowlist";
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 491a5cda2950..e7eda3ea4552 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -5898,14 +5898,8 @@ public class AudioManager {
*/
@UnsupportedAppUsage
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
- public void setWiredDeviceConnectionState(int device, int state, String address,
- String name) {
- final IAudioService service = getService();
- int role = isOutputDevice(device)
- ? AudioDeviceAttributes.ROLE_OUTPUT : AudioDeviceAttributes.ROLE_INPUT;
- AudioDeviceAttributes attributes = new AudioDeviceAttributes(
- role, AudioDeviceInfo.convertInternalDeviceToDeviceType(device), address,
- name, new ArrayList<>()/*mAudioProfiles*/, new ArrayList<>()/*mAudioDescriptors*/);
+ public void setWiredDeviceConnectionState(int device, int state, String address, String name) {
+ AudioDeviceAttributes attributes = new AudioDeviceAttributes(device, address, name);
setWiredDeviceConnectionState(attributes, state);
}
diff --git a/media/tests/aidltests/Android.bp b/media/tests/aidltests/Android.bp
index c3d5fa2ae224..7a25b6d40c1f 100644
--- a/media/tests/aidltests/Android.bp
+++ b/media/tests/aidltests/Android.bp
@@ -33,7 +33,6 @@ android_test {
libs: [
"framework",
],
- sdk_version: "current",
platform_apis: true,
test_suites: ["device-tests"],
certificate: "platform",
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index c2e36b79b753..e1a2e8daf971 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -28,6 +28,7 @@ import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@@ -239,13 +240,24 @@ public class LocalMediaManager implements BluetoothCallback {
/**
* Dispatch a change in the about-to-connect device. See
- * {@link DeviceCallback#onAboutToConnectDeviceChanged} for more information.
+ * {@link DeviceCallback#onAboutToConnectDeviceAdded} for more information.
*/
- public void dispatchAboutToConnectDeviceChanged(
- @Nullable String deviceName,
+ public void dispatchAboutToConnectDeviceAdded(
+ @NonNull String deviceAddress,
+ @NonNull String deviceName,
@Nullable Drawable deviceIcon) {
for (DeviceCallback callback : getCallbacks()) {
- callback.onAboutToConnectDeviceChanged(deviceName, deviceIcon);
+ callback.onAboutToConnectDeviceAdded(deviceAddress, deviceName, deviceIcon);
+ }
+ }
+
+ /**
+ * Dispatch a change in the about-to-connect device. See
+ * {@link DeviceCallback#onAboutToConnectDeviceRemoved} for more information.
+ */
+ public void dispatchAboutToConnectDeviceRemoved() {
+ for (DeviceCallback callback : getCallbacks()) {
+ callback.onAboutToConnectDeviceRemoved();
}
}
@@ -705,13 +717,27 @@ public class LocalMediaManager implements BluetoothCallback {
* connect imminently and should be displayed as the current device in the media player.
* See [AudioManager.muteAwaitConnection] for more details.
*
- * @param deviceName the name of the device (displayed to the user).
- * @param deviceIcon the icon that should be used with the device.
+ * The information in the most recent callback should override information from any previous
+ * callbacks.
+ *
+ * @param deviceAddress the address of the device. {@see AudioDeviceAttributes.address}.
+ * If present, we'll use this address to fetch the full information
+ * about the device (if we can find that information).
+ * @param deviceName the name of the device (displayed to the user). Used as a backup in
+ * case using deviceAddress doesn't work.
+ * @param deviceIcon the icon that should be used with the device. Used as a backup in case
+ * using deviceAddress doesn't work.
*/
- default void onAboutToConnectDeviceChanged(
- @Nullable String deviceName,
+ default void onAboutToConnectDeviceAdded(
+ @NonNull String deviceAddress,
+ @NonNull String deviceName,
@Nullable Drawable deviceIcon
) {}
+
+ /**
+ * Callback for notifying that we no longer have an about-to-connect device.
+ */
+ default void onAboutToConnectDeviceRemoved() {}
}
/**
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index a10ca9e75355..a28c4cf6b0d4 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -575,6 +575,9 @@
<uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
<uses-permission android:name="android.permission.TRUST_LISTENER" />
+ <!-- Permission required for CTS test - CtsTaskFpsCallbackTestCases -->
+ <uses-permission android:name="android.permission.ACCESS_FPS_COUNTER" />
+
<!-- Permission required for CTS test - CtsGameManagerTestCases -->
<uses-permission android:name="android.permission.MANAGE_GAME_MODE" />
diff --git a/packages/SystemUI/docs/keyguard/bouncer.md b/packages/SystemUI/docs/device-entry/bouncer.md
index 4bfe7340db30..589cb5d300d3 100644
--- a/packages/SystemUI/docs/keyguard/bouncer.md
+++ b/packages/SystemUI/docs/device-entry/bouncer.md
@@ -2,6 +2,8 @@
[KeyguardBouncer][1] is the component responsible for displaying the security method set by the user (password, PIN, pattern) as well as SIM-related security methods, allowing the user to unlock the device or SIM.
+![ss-bouncer](./imgs/bouncer_pin.png)
+
## Supported States
1. Phone, portrait mode - The default and typically only way to view the bouncer. Screen cannot rotate.
diff --git a/packages/SystemUI/docs/keyguard/doze.md b/packages/SystemUI/docs/device-entry/doze.md
index a6ccab9698d4..5ff8851b4c69 100644
--- a/packages/SystemUI/docs/keyguard/doze.md
+++ b/packages/SystemUI/docs/device-entry/doze.md
@@ -2,6 +2,8 @@
Always-on Display (AOD) provides an alternative 'screen-off' experience. Instead, of completely turning the display off, it provides a distraction-free, glanceable experience for the phone in a low-powered mode. In this low-powered mode, the display will have a lower refresh rate and the UI should frequently shift its displayed contents in order to prevent burn-in. The recommended max on-pixel-ratio (OPR) is 5% to reduce battery consumption.
+![ss-aod](./imgs/aod.png)
+
The default doze component controls AOD and is specified by `config_dozeComponent` in the [framework config][1]. SystemUI provides a default Doze Component: [DozeService][2]. [DozeService][2] builds a [DozeMachine][3] with dependencies specified in [DozeModule][4] and configurations in [AmbientDisplayConfiguration][13] and [DozeParameters][14].
Note: The default UI used in AOD shares views with the Lock Screen and does not create its own new views. Once dozing begins, [DozeUI][17] informs SystemUI's [DozeServiceHost][18] that dozing has begun - which sends this signal to relevant SystemUI Lock Screen views to animate accordingly. Within SystemUI, [StatusBarStateController][19] #isDozing and #getDozeAmount can be used to query dozing state.
diff --git a/packages/SystemUI/docs/device-entry/glossary.md b/packages/SystemUI/docs/device-entry/glossary.md
new file mode 100644
index 000000000000..f3d12c21a3a5
--- /dev/null
+++ b/packages/SystemUI/docs/device-entry/glossary.md
@@ -0,0 +1,48 @@
+# Device Entry Glossary
+
+## Keyguard
+
+| Term | Description |
+| :-----------: | ----------- |
+| Keyguard, [keyguard.md][1] | Coordinates the first experience when turning on the display of a device, as long as the user has not specified a security method of NONE. Consists of the lock screen and bouncer.|
+| Lock screen<br><br>![ss_aod](imgs/lockscreen.png)| The first screen available when turning on the display of a device, as long as the user has not specified a security method of NONE. On the lock screen, users can access:<ul><li>Quick Settings - users can swipe down from the top of the screen to interact with quick settings tiles</li><li>[Keyguard Status Bar][9] - This special status bar shows SIM related information and system icons.</li><li>Clock - uses the font specified at [clock.xml][8]. If the clock font supports variable weights, users will experience delightful clock weight animations - in particular, on transitions between the lock screen and AOD.</li><li>Notifications - ability to view and interact with notifications depending on user lock screen notification settings: `Settings > Display > Lock screen > Privacy`</li><li>Message area - contains device information like biometric errors, charging information and device policy information. Also includes user configured information from `Settings > Display > Lock screen > Add text on lock screen`. </li><li>Bouncer - if the user has a primary authentication method, they can swipe up from the bottom of the screen to bring up the bouncer.</li></ul>The lock screen is one state of the notification shade. See [StatusBarState#KEYGUARD][10] and [StatusBarState#SHADE_LOCKED][10].|
+| Bouncer, [bouncer.md][2]<br><br>![ss_aod](imgs/bouncer_pin.png)| The component responsible for displaying the primary security method set by the user (password, PIN, pattern). The bouncer can also show SIM-related security methods, allowing the user to unlock the device or SIM.|
+| Split shade | State of the shade (which keyguard is a part of) in which notifications are on the right side and Quick Settings on the left. For keyguard that means notifications being on the right side and clock with media being on the left.<br><br>Split shade is automatically activated - using resources - for big screens in landscape, see [sw600dp-land/config.xml][3] `config_use_split_notification_shade`.<br><br>In that state we can see the big clock more often - every time when media is not visible on the lock screen. When there is no media and no notifications - or we enter AOD - big clock is always positioned in the center of the screen.<br><br>The magic of positioning views happens by changing constraints of [NotificationsQuickSettingsContainer][4] and positioning elements vertically in [KeyguardClockPositionAlgorithm][5]|
+| Ambient display (AOD), [doze.md][6]<br><br>![ss_aod](imgs/aod.png)| UI shown when the device is in a low-powered display state. This is controlled by the doze component. The same lock screen views (ie: clock, notification shade) are used on AOD. The AOSP image on the left shows the usage of a clock that does not support variable weights which is why the clock is thicker in that image than what users see on Pixel devices.|
+
+## General Authentication Terms
+| Term | Description |
+| ----------- | ----------- |
+| Primary Authentication | The strongest form of authentication. Includes: Pin, pattern and password input.|
+| Biometric Authentication | Face or fingerprint input. Biometric authentication is categorized into different classes of security. See [Measuring Biometric Security][7].|
+
+## Face Authentication Terms
+| Term | Description |
+| ----------- | ----------- |
+| Passive Authentication | When a user hasn't explicitly requested an authentication method; however, it may still put the device in an unlocked state.<br><br>For example, face authentication is triggered immediately when waking the device; however, users may not have the intent of unlocking their device. Instead, they could have wanted to just check the lock screen. Because of this, SystemUI provides the option for a bypass OR non-bypass face authentication experience which have different user flows.<br><br>In contrast, fingerprint authentication is considered an active authentication method since users need to actively put their finger on the fingerprint sensor to authenticate. Therefore, it's an explicit request for authentication and SystemUI knows the user has the intent for device-entry.|
+| Bypass | Used to refer to the face authentication bypass device entry experience. We have this distinction because face auth is a passive authentication method (see above).|
+| Bypass User Journey <br><br>![ss_bypass](imgs/bypass.png)| Once the user successfully authenticates with face, the keyguard immediately dismisses and the user is brought to the home screen/last app. This CUJ prioritizes speed of device entry. SystemUI hides interactive views (notifications) on the lock screen to avoid putting users in a state where the lock screen could immediately disappear while they're interacting with affordances on the lock screen.|
+| Non-bypass User Journey | Once the user successfully authenticates with face, the device remains on keyguard until the user performs an action to indicate they'd like to enter the device (ie: swipe up on the lock screen or long press on the unlocked icon). This CUJ prioritizes notification visibility.|
+
+## Fingerprint Authentication Terms
+| Term | Description |
+| ----------- | ----------- |
+| Under-display fingerprint sensor (UDFPS) | References the HW affordance for a fingerprint sensor that is under the display, which requires a software visual affordance. System UI supports showing the UDFPS affordance on the lock screen and on AOD. Users cannot authenticate from the screen-off state.<br><br>Supported SystemUI CUJs include:<ul><li> sliding finger on the screen to the UDFPS area to being authentication (as opposed to directly placing finger in the UDFPS area) </li><li> when a11y services are enabled, there is a haptic played when a touch is detected on UDFPS</li><li>after two hard-fingerprint-failures, the primary authentication bouncer is shown</li><li> when tapping on an affordance that requests to dismiss the lock screen, the user may see the UDFPS icon highlighted - see UDFPS bouncer</li></ul>|
+| UDFPS Bouncer | UI that highlights the UDFPS sensor. Users can get into this state after tapping on a notification from the lock screen or locked expanded shade.|
+
+## Other Authentication Terms
+| Term | Description |
+| ---------- | ----------- |
+| Trust Agents | Provides signals to the keyguard to allow it to lock less frequently.|
+
+
+[1]: /frameworks/base/packages/SystemUI/docs/device-entry/keyguard.md
+[2]: /frameworks/base/packages/SystemUI/docs/device-entry/bouncer.md
+[3]: /frameworks/base/packages/SystemUI/res/values-sw600dp-land/config.xml
+[4]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+[5]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+[6]: /frameworks/base/packages/SystemUI/docs/device-entry/doze.md
+[7]: https://source.android.com/security/biometric/measure
+[8]: /frameworks/base/packages/SystemUI/res-keyguard/font/clock.xml
+[9]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+[10]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarState.java
diff --git a/packages/SystemUI/docs/device-entry/imgs/aod.png b/packages/SystemUI/docs/device-entry/imgs/aod.png
new file mode 100644
index 000000000000..abd554a8936f
--- /dev/null
+++ b/packages/SystemUI/docs/device-entry/imgs/aod.png
Binary files differ
diff --git a/packages/SystemUI/docs/device-entry/imgs/bouncer_pin.png b/packages/SystemUI/docs/device-entry/imgs/bouncer_pin.png
new file mode 100644
index 000000000000..da15e4115ab9
--- /dev/null
+++ b/packages/SystemUI/docs/device-entry/imgs/bouncer_pin.png
Binary files differ
diff --git a/packages/SystemUI/docs/device-entry/imgs/bypass.png b/packages/SystemUI/docs/device-entry/imgs/bypass.png
new file mode 100644
index 000000000000..f4cbd3efb6fc
--- /dev/null
+++ b/packages/SystemUI/docs/device-entry/imgs/bypass.png
Binary files differ
diff --git a/packages/SystemUI/docs/device-entry/imgs/lockscreen.png b/packages/SystemUI/docs/device-entry/imgs/lockscreen.png
new file mode 100644
index 000000000000..d1fe0853f578
--- /dev/null
+++ b/packages/SystemUI/docs/device-entry/imgs/lockscreen.png
Binary files differ
diff --git a/packages/SystemUI/docs/keyguard.md b/packages/SystemUI/docs/device-entry/keyguard.md
index 8914042ee3cd..337f73b79260 100644
--- a/packages/SystemUI/docs/keyguard.md
+++ b/packages/SystemUI/docs/device-entry/keyguard.md
@@ -30,6 +30,12 @@ An indication to power off the device most likely comes from one of two signals:
### How the device locks
+## Debugging Tips
+Enable verbose keyguard logs that will print to logcat. Should only be used temporarily for debugging. See [KeyguardConstants][5].
+```
+adb shell setprop log.tag.Keyguard DEBUG && adb shell am crash com.android.systemui
+```
+
More coming
* Screen timeout
* Smart lock
@@ -38,9 +44,8 @@ More coming
* Lock timeout after screen timeout setting
-[1]: /frameworks/base/packages/SystemUI/docs/keyguard/bouncer.md
-[2]: /frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
-[3]: /frameworks/base/packages/SystemUI/docs/keyguard/doze.md
-[4]: /frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
-
-
+[1]: /frameworks/base/packages/SystemUI/docs/device-entry/bouncer.md
+[2]: /com/android/server/power/PowerManagerService.java
+[3]: /frameworks/base/packages/SystemUI/docs/device-entry/doze.md
+[4]: /com/android/server/policy/PhoneWindowManager.java
+[5]: /frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java
diff --git a/packages/SystemUI/docs/user-switching.md b/packages/SystemUI/docs/user-switching.md
index dcf66b943f1d..b9509eb41c3c 100644
--- a/packages/SystemUI/docs/user-switching.md
+++ b/packages/SystemUI/docs/user-switching.md
@@ -37,7 +37,7 @@ A fullscreen user switching activity, supporting add guest/user actions if confi
Renders user switching as a dialog over the current surface, and supports add guest user/actions if configured.
-[1]: /frameworks/base/packages/SystemUI/docs/keyguard/bouncer.md
+[1]: /frameworks/base/packages/SystemUI/docs/device-entry/bouncer.md
[2]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserController.java
[3]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
[4]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
diff --git a/packages/SystemUI/res-keyguard/layout/fgs_footer.xml b/packages/SystemUI/res-keyguard/layout/fgs_footer.xml
index 9d801d2a0ecf..9ffafbc8cc09 100644
--- a/packages/SystemUI/res-keyguard/layout/fgs_footer.xml
+++ b/packages/SystemUI/res-keyguard/layout/fgs_footer.xml
@@ -16,8 +16,9 @@
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
+ android:layout_width="0dp"
android:layout_height="@dimen/qs_security_footer_single_line_height"
+ android:layout_weight="1"
android:gravity="center"
android:clickable="true"
android:visibility="gone">
@@ -26,7 +27,7 @@
android:id="@+id/fgs_text_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_marginEnd="@dimen/new_qs_footer_action_inset"
+ android:layout_marginEnd="@dimen/qs_footer_action_inset"
android:background="@drawable/qs_security_footer_background"
android:layout_gravity="end"
android:gravity="center"
@@ -86,7 +87,7 @@
android:layout_height="12dp"
android:scaleType="fitCenter"
android:layout_gravity="bottom|end"
- android:src="@drawable/new_fgs_dot"
+ android:src="@drawable/fgs_dot"
android:contentDescription="@string/fgs_dot_content_description"
/>
</FrameLayout>
diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions.xml b/packages/SystemUI/res-keyguard/layout/footer_actions.xml
index fb401ee1d918..6a1d62d5c611 100644
--- a/packages/SystemUI/res-keyguard/layout/footer_actions.xml
+++ b/packages/SystemUI/res-keyguard/layout/footer_actions.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
-** Copyright 2021, The Android Open Source Project
+** Copyright 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.
@@ -18,65 +18,80 @@
<!-- Action buttons for footer in QS/QQS, containing settings button, power off button etc -->
<com.android.systemui.qs.FooterActionsView
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:layout_width="match_parent"
- android:layout_height="@dimen/qs_footer_height"
+ android:layout_height="@dimen/footer_actions_height"
+ android:elevation="@dimen/qs_panel_elevation"
+ android:paddingTop="8dp"
+ android:paddingBottom="4dp"
+ android:background="@drawable/qs_footer_actions_background"
android:gravity="center_vertical"
android:layout_gravity="bottom"
>
- <com.android.systemui.statusbar.phone.MultiUserSwitch
- android:id="@+id/multi_user_switch"
- android:layout_width="0dp"
+ <LinearLayout
+ android:id="@+id/security_footers_container"
+ android:orientation="horizontal"
android:layout_height="@dimen/qs_footer_action_button_size"
- android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
+ android:layout_width="0dp"
android:layout_weight="1"
- android:background="@drawable/qs_footer_action_chip_background"
- android:focusable="true">
+ />
- <ImageView
- android:id="@+id/multi_user_avatar"
- android:layout_width="@dimen/multi_user_avatar_expanded_size"
- android:layout_height="@dimen/multi_user_avatar_expanded_size"
- android:layout_gravity="center"
- android:scaleType="centerInside" />
- </com.android.systemui.statusbar.phone.MultiUserSwitch>
+ <!-- Negative margin equal to -->
+ <LinearLayout
+ android:layout_height="match_parent"
+ android:layout_width="wrap_content"
+ android:layout_marginEnd="@dimen/qs_footer_action_inset_negative"
+ >
- <com.android.systemui.statusbar.AlphaOptimizedImageView
- android:id="@+id/pm_lite"
- android:layout_width="0dp"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
- android:layout_weight="1"
- android:background="@drawable/qs_footer_action_chip_background"
- android:clickable="true"
- android:clipToPadding="false"
- android:focusable="true"
- android:padding="@dimen/qs_footer_icon_padding"
- android:src="@*android:drawable/ic_lock_power_off"
- android:contentDescription="@string/accessibility_quick_settings_power_menu"
- android:tint="?android:attr/textColorPrimary" />
+ <com.android.systemui.statusbar.phone.MultiUserSwitch
+ android:id="@+id/multi_user_switch"
+ android:layout_width="@dimen/qs_footer_action_button_size"
+ android:layout_height="@dimen/qs_footer_action_button_size"
+ android:background="@drawable/qs_footer_action_circle"
+ android:focusable="true">
- <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
- android:id="@+id/settings_button_container"
- android:layout_width="0dp"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:background="@drawable/qs_footer_action_chip_background"
- android:layout_weight="1"
- android:clipChildren="false"
- android:clipToPadding="false">
+ <ImageView
+ android:id="@+id/multi_user_avatar"
+ android:layout_width="@dimen/qs_footer_icon_size"
+ android:layout_height="@dimen/qs_footer_icon_size"
+ android:layout_gravity="center"
+ android:scaleType="centerInside" />
+ </com.android.systemui.statusbar.phone.MultiUserSwitch>
- <com.android.systemui.statusbar.phone.SettingsButton
- android:id="@+id/settings_button"
- android:layout_width="match_parent"
+ <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
+ android:id="@+id/settings_button_container"
+ android:layout_width="@dimen/qs_footer_action_button_size"
android:layout_height="@dimen/qs_footer_action_button_size"
- android:layout_gravity="center"
- android:contentDescription="@string/accessibility_quick_settings_settings"
- android:background="@drawable/qs_footer_action_chip_background_borderless"
- android:padding="@dimen/qs_footer_icon_padding"
- android:scaleType="centerInside"
- android:src="@drawable/ic_settings"
- android:tint="?android:attr/textColorPrimary" />
+ android:background="@drawable/qs_footer_action_circle"
+ android:clipChildren="false"
+ android:clipToPadding="false">
+
+ <com.android.systemui.statusbar.phone.SettingsButton
+ android:id="@+id/settings_button"
+ android:layout_width="@dimen/qs_footer_icon_size"
+ android:layout_height="@dimen/qs_footer_icon_size"
+ android:layout_gravity="center"
+ android:background="@android:color/transparent"
+ android:contentDescription="@string/accessibility_quick_settings_settings"
+ android:scaleType="centerInside"
+ android:src="@drawable/ic_settings"
+ android:tint="?android:attr/textColorPrimary" />
- </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
+ </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
+
+ <com.android.systemui.statusbar.AlphaOptimizedImageView
+ android:id="@+id/pm_lite"
+ android:layout_width="@dimen/qs_footer_action_button_size"
+ android:layout_height="@dimen/qs_footer_action_button_size"
+ android:background="@drawable/qs_footer_action_circle_color"
+ android:clickable="true"
+ android:clipToPadding="false"
+ android:focusable="true"
+ android:padding="@dimen/qs_footer_icon_padding"
+ android:src="@*android:drawable/ic_lock_power_off"
+ android:contentDescription="@string/accessibility_quick_settings_power_menu"
+ android:tint="?androidprv:attr/textColorOnAccent" />
+ </LinearLayout>
</com.android.systemui.qs.FooterActionsView> \ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml b/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml
deleted file mode 100644
index 59712c088f7a..000000000000
--- a/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml
+++ /dev/null
@@ -1,97 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-** Copyright 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.
--->
-
-<!-- Action buttons for footer in QS/QQS, containing settings button, power off button etc -->
-<com.android.systemui.qs.FooterActionsView
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:layout_width="match_parent"
- android:layout_height="@dimen/new_footer_height"
- android:elevation="@dimen/qs_panel_elevation"
- android:paddingTop="8dp"
- android:paddingBottom="4dp"
- android:background="@drawable/qs_footer_actions_background"
- android:gravity="center_vertical"
- android:layout_gravity="bottom"
->
-
- <LinearLayout
- android:id="@+id/security_footers_container"
- android:orientation="horizontal"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:layout_width="0dp"
- android:layout_weight="1"
- />
-
- <!-- Negative margin equal to -->
- <LinearLayout
- android:layout_height="match_parent"
- android:layout_width="wrap_content"
- android:layout_marginEnd="@dimen/new_qs_footer_action_inset_negative"
- >
-
- <com.android.systemui.statusbar.phone.MultiUserSwitch
- android:id="@+id/multi_user_switch"
- android:layout_width="@dimen/qs_footer_action_button_size"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:background="@drawable/qs_footer_action_circle"
- android:focusable="true">
-
- <ImageView
- android:id="@+id/multi_user_avatar"
- android:layout_width="@dimen/qs_footer_icon_size"
- android:layout_height="@dimen/qs_footer_icon_size"
- android:layout_gravity="center"
- android:scaleType="centerInside" />
- </com.android.systemui.statusbar.phone.MultiUserSwitch>
-
- <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
- android:id="@+id/settings_button_container"
- android:layout_width="@dimen/qs_footer_action_button_size"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:background="@drawable/qs_footer_action_circle"
- android:clipChildren="false"
- android:clipToPadding="false">
-
- <com.android.systemui.statusbar.phone.SettingsButton
- android:id="@+id/settings_button"
- android:layout_width="@dimen/qs_footer_icon_size"
- android:layout_height="@dimen/qs_footer_icon_size"
- android:layout_gravity="center"
- android:background="@android:color/transparent"
- android:contentDescription="@string/accessibility_quick_settings_settings"
- android:scaleType="centerInside"
- android:src="@drawable/ic_settings"
- android:tint="?android:attr/textColorPrimary" />
-
- </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
-
- <com.android.systemui.statusbar.AlphaOptimizedImageView
- android:id="@+id/pm_lite"
- android:layout_width="@dimen/qs_footer_action_button_size"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:background="@drawable/qs_footer_action_circle_color"
- android:clickable="true"
- android:clipToPadding="false"
- android:focusable="true"
- android:padding="@dimen/qs_footer_icon_padding"
- android:src="@*android:drawable/ic_lock_power_off"
- android:contentDescription="@string/accessibility_quick_settings_power_menu"
- android:tint="?androidprv:attr/textColorOnAccent" />
-
- </LinearLayout>
-</com.android.systemui.qs.FooterActionsView> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/new_fgs_dot.xml b/packages/SystemUI/res/drawable/fgs_dot.xml
index 3669e1d3c374..3669e1d3c374 100644
--- a/packages/SystemUI/res/drawable/new_fgs_dot.xml
+++ b/packages/SystemUI/res/drawable/fgs_dot.xml
diff --git a/packages/SystemUI/res/drawable/ic_media_connecting_container.xml b/packages/SystemUI/res/drawable/ic_media_connecting_container.xml
new file mode 100644
index 000000000000..79d2a0601e08
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_media_connecting_container.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="48dp"
+ android:width="48dp"
+ android:viewportHeight="48"
+ android:viewportWidth="48">
+ <group android:name="_R_G">
+ <group android:name="_R_G_L_1_G"
+ android:translateX="24"
+ android:translateY="24"
+ android:scaleX="0.5"
+ android:scaleY="0.5"/>
+ <group android:name="_R_G_L_0_G"
+ android:translateX="24"
+ android:translateY="24"
+ android:scaleX="0.5"
+ android:scaleY="0.5">
+ <path android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillColor="#ffddb3"
+ android:fillAlpha="1"
+ android:fillType="nonZero"
+ android:pathData=" M48 -16 C48,-16 48,16 48,16 C48,33.67 33.67,48 16,48 C16,48 -16,48 -16,48 C-33.67,48 -48,33.67 -48,16 C-48,16 -48,-16 -48,-16 C-48,-33.67 -33.67,-48 -16,-48 C-16,-48 16,-48 16,-48 C33.67,-48 48,-33.67 48,-16c "/>
+ </group>
+ </group>
+</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_chip_background.xml b/packages/SystemUI/res/drawable/qs_footer_action_chip_background.xml
deleted file mode 100644
index 9076da795e71..000000000000
--- a/packages/SystemUI/res/drawable/qs_footer_action_chip_background.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2021 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:insetTop="@dimen/qs_footer_action_inset"
- android:insetBottom="@dimen/qs_footer_action_inset">
- <ripple
- android:color="?android:attr/colorControlHighlight"
- android:height="44dp">
- <item android:id="@android:id/mask">
- <shape android:shape="rectangle">
- <solid android:color="@android:color/white"/>
- <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
- </shape>
- </item>
- <item>
- <shape android:shape="rectangle">
- <solid android:color="?attr/underSurfaceColor"/>
- <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
- </shape>
- </item>
- <item>
- <shape android:shape="rectangle">
- <stroke android:width="1dp" android:color="?android:attr/colorBackground"/>
- <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
- </shape>
- </item>
- </ripple>
-</inset> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_chip_background_borderless.xml b/packages/SystemUI/res/drawable/qs_footer_action_chip_background_borderless.xml
deleted file mode 100644
index bbcfb15d9226..000000000000
--- a/packages/SystemUI/res/drawable/qs_footer_action_chip_background_borderless.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2021 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:insetTop="@dimen/qs_footer_action_inset"
- android:insetBottom="@dimen/qs_footer_action_inset">
- <ripple
- android:color="?android:attr/colorControlHighlight">
- <item android:id="@android:id/mask">
- <shape android:shape="rectangle">
- <solid android:color="@android:color/white"/>
- <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
- </shape>
- </item>
- <item>
- <shape android:shape="rectangle">
- <solid android:color="@android:color/transparent"/>
- <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
- </shape>
- </item>
- </ripple>
-</inset> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
index 31a8c3bc2397..c8c36b0081c0 100644
--- a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
+++ b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:inset="@dimen/new_qs_footer_action_inset">
+ android:inset="@dimen/qs_footer_action_inset">
<ripple
android:color="?android:attr/colorControlHighlight">
<item android:id="@android:id/mask">
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
index 021a85f6a244..6a365000a21c 100644
--- a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
+++ b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:inset="@dimen/new_qs_footer_action_inset">
+ android:inset="@dimen/qs_footer_action_inset">
<ripple
android:color="?android:attr/colorControlHighlight">
<item android:id="@android:id/mask">
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index b6e34998a7cd..b1d3ed05333b 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -29,11 +29,6 @@
android:clipChildren="false"
android:clipToPadding="false">
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
<LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/qs_footer_height"
@@ -80,14 +75,4 @@
</LinearLayout>
- <ViewStub
- android:id="@+id/footer_stub"
- android:inflatedId="@+id/qs_footer_actions"
- android:layout="@layout/footer_actions"
- android:layout_height="@dimen/qs_footer_height"
- android:layout_width="match_parent"
- />
-
- </LinearLayout>
-
</com.android.systemui.qs.QSFooterView>
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index 20400510a31a..1eb05bfd602d 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -47,11 +47,10 @@
<include layout="@layout/quick_status_bar_expanded_header" />
- <ViewStub
- android:id="@+id/container_stub"
- android:inflatedId="@+id/qs_footer_actions"
- android:layout="@layout/new_footer_actions"
- android:layout_height="@dimen/new_footer_height"
+ <include
+ layout="@layout/footer_actions"
+ android:id="@+id/qs_footer_actions"
+ android:layout_height="@dimen/footer_actions_height"
android:layout_width="match_parent"
android:layout_gravity="bottom"
/>
diff --git a/packages/SystemUI/res/layout/quick_settings_security_footer.xml b/packages/SystemUI/res/layout/quick_settings_security_footer.xml
index 08bd71c12eb1..1b11816465ac 100644
--- a/packages/SystemUI/res/layout/quick_settings_security_footer.xml
+++ b/packages/SystemUI/res/layout/quick_settings_security_footer.xml
@@ -14,19 +14,18 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.systemui.util.DualHeightHorizontalLinearLayout
+<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:systemui="http://schemas.android.com/apk/res-auto"
- android:layout_width="match_parent"
- android:layout_height="@dimen/qs_security_footer_height"
+ android:layout_width="0dp"
+ android:layout_height="@dimen/qs_security_footer_single_line_height"
+ android:layout_weight="1"
android:clickable="true"
- android:padding="@dimen/qs_footer_padding"
+ android:orientation="horizontal"
+ android:paddingHorizontal="@dimen/qs_footer_padding"
android:gravity="center_vertical"
android:layout_gravity="center_vertical|center_horizontal"
- android:layout_marginBottom="@dimen/qs_footers_margin_bottom"
+ android:layout_marginEnd="@dimen/qs_footer_action_inset"
android:background="@drawable/qs_security_footer_background"
- systemui:singleLineHeight="@dimen/qs_security_footer_single_line_height"
- systemui:textViewId="@id/footer_text"
>
<ImageView
@@ -43,7 +42,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
- android:maxLines="@integer/qs_security_footer_maxLines"
+ android:singleLine="true"
android:ellipsize="end"
android:textAppearance="@style/TextAppearance.QS.SecurityFooter"
android:textColor="?android:attr/textColorSecondary"/>
@@ -58,4 +57,4 @@
android:autoMirrored="true"
android:tint="?android:attr/textColorSecondary" />
-</com.android.systemui.util.DualHeightHorizontalLinearLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml b/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
index b1e8c386fe21..60bc3732cde0 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
@@ -52,7 +52,6 @@
<!-- We want this to be centered (to align with notches). In order to do that, the following
has to hold (in portrait):
* date_container and privacy_container must have the same width and weight
- * header_text_container must be gone
-->
<android.widget.Space
android:id="@+id/space"
@@ -61,17 +60,6 @@
android:layout_gravity="center_vertical|center_horizontal"
android:visibility="gone" />
- <!-- Will hold security footer in landscape with media -->
- <FrameLayout
- android:id="@+id/header_text_container"
- android:layout_height="match_parent"
- android:layout_width="0dp"
- android:layout_weight="1"
- android:paddingStart="16dp"
- android:paddingEnd="16dp"
- android:gravity="center"
- />
-
<FrameLayout
android:id="@+id/privacy_container"
android:layout_width="0dp"
diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml
index 8de80844d784..c60609b06d38 100644
--- a/packages/SystemUI/res/layout/screenshot_static.xml
+++ b/packages/SystemUI/res/layout/screenshot_static.xml
@@ -98,6 +98,7 @@
android:scaleType="fitEnd"
android:background="@drawable/overlay_preview_background"
android:adjustViewBounds="true"
+ android:clickable="true"
app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"
app:layout_constraintStart_toStartOf="@id/screenshot_preview_border"
app:layout_constraintEnd_toEndOf="@id/screenshot_preview_border"
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 62903d5a7b35..3228c3c9d37f 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -188,12 +188,6 @@
<attr name="borderColor" format="color" />
</declare-styleable>
- <declare-styleable name="DualHeightHorizontalLinearLayout">
- <attr name="singleLineHeight" format="dimension" />
- <attr name="singleLineVerticalPadding" format="dimension" />
- <attr name="textViewId" format="reference" />
- </declare-styleable>
-
<attr name="overlayButtonTextColor" format="color" />
<declare-styleable name="DreamOverlayDotImageView">
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 50fdc7bb61bd..24118d256fcc 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -137,9 +137,11 @@
<!-- UDFPS colors -->
<color name="udfps_enroll_icon">#7DA7F1</color>
<color name="udfps_moving_target_fill">#475670</color>
+ <!-- 50% of udfps_moving_target_fill-->
+ <color name="udfps_moving_target_fill_error">#80475670</color>
<color name="udfps_enroll_progress">#7DA7F1</color>
<color name="udfps_enroll_progress_help">#607DA7F1</color>
- <color name="udfps_enroll_progress_help_with_talkback">#ffEE675C</color>
+ <color name="udfps_enroll_progress_help_with_talkback">#FFEE675C</color>
<!-- Floating overlay actions -->
<color name="overlay_button_ripple">#1f000000</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 851c6ddc2f86..767225106f0d 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -327,23 +327,20 @@
car setting. -->
<dimen name="car_qs_header_system_icons_area_height">54dp</dimen>
- <!-- The height of the quick settings footer that holds the user switcher, settings icon,
- etc. -->
+ <!-- The height of the quick settings footer that holds the pagination dots and edit button -->
<dimen name="qs_footer_height">48dp</dimen>
<!-- 40dp (circles) + 8dp (circle padding) + 8dp (top) + 4dp (bottom) -->
- <dimen name="new_footer_height">60dp</dimen>
+ <dimen name="footer_actions_height">60dp</dimen>
<!-- The size of each of the icon buttons in the QS footer -->
<dimen name="qs_footer_action_button_size">48dp</dimen>
<dimen name="qs_footer_action_corner_radius">20dp</dimen>
- <!-- (48dp - 44dp) / 2 -->
- <dimen name="qs_footer_action_inset">2dp</dimen>
<!-- (48dp - 40dp) / 2 -->
- <dimen name="new_qs_footer_action_inset">4dp</dimen>
- <dimen name="new_qs_footer_action_inset_negative">-4dp</dimen>
+ <dimen name="qs_footer_action_inset">4dp</dimen>
+ <dimen name="qs_footer_action_inset_negative">-4dp</dimen>
<!-- Margins on each side of QS Footer -->
<dimen name="qs_footer_margin">2dp</dimen>
@@ -495,7 +492,7 @@
<dimen name="qs_panel_padding">16dp</dimen>
<dimen name="qs_dual_tile_padding_horizontal">6dp</dimen>
<dimen name="qs_panel_elevation">4dp</dimen>
- <dimen name="qs_panel_padding_bottom">@dimen/new_footer_height</dimen>
+ <dimen name="qs_panel_padding_bottom">@dimen/footer_actions_height</dimen>
<dimen name="qs_panel_padding_top">48dp</dimen>
<dimen name="qs_data_usage_text_size">14sp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 926734c2749f..ff71b4f5e405 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -23,6 +23,7 @@
<item type="id" name="scale_x_animator_tag"/>
<item type="id" name="scale_y_animator_tag"/>
<item type="id" name="top_inset_animator_tag"/>
+ <item type="id" name="bottom_inset_animator_tag"/>
<item type="id" name="height_animator_tag"/>
<item type="id" name="x_animator_tag"/>
<item type="id" name="y_animator_tag"/>
@@ -33,6 +34,7 @@
<item type="id" name="scale_y_animator_end_value_tag"/>
<item type="id" name="alpha_animator_end_value_tag"/>
<item type="id" name="top_inset_animator_end_value_tag"/>
+ <item type="id" name="bottom_inset_animator_end_value_tag"/>
<item type="id" name="height_animator_end_value_tag"/>
<item type="id" name="x_animator_tag_end_value"/>
<item type="id" name="y_animator_tag_end_value"/>
@@ -43,6 +45,7 @@
<item type="id" name="scale_y_animator_start_value_tag"/>
<item type="id" name="alpha_animator_start_value_tag"/>
<item type="id" name="top_inset_animator_start_value_tag"/>
+ <item type="id" name="bottom_inset_animator_start_value_tag"/>
<item type="id" name="height_animator_start_value_tag"/>
<item type="id" name="x_animator_tag_start_value"/>
<item type="id" name="y_animator_tag_start_value"/>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 33ed7b8421db..b248efe93e98 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2199,6 +2199,8 @@
<string name="controls_media_button_prev">Previous track</string>
<!-- Description for button in media controls. Pressing button goes to next track [CHAR_LIMIT=NONE] -->
<string name="controls_media_button_next">Next track</string>
+ <!-- Description for button in media controls. Used when media is connecting to a remote device (via something like chromecast). Pressing button does nothing [CHAR_LIMIT=NONE] -->
+ <string name="controls_media_button_connecting">Connecting</string>
<!-- Title for Smartspace recommendation card within media controls. The "Play" means the action to play a media [CHAR_LIMIT=10] -->
<string name="controls_media_smartspace_rec_title">Play</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 847aefdd67ce..ee77d210add5 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -958,6 +958,16 @@
<item name="android:textAlignment">center</item>
</style>
+ <!-- We explicitly overload this because we don't have control over the style or layout for
+ the cast dialog items, as it's in `@android:layout/media_route_list_item. -->
+ <style name="TextAppearance.CastItem" parent="@android:style/TextAppearance.DeviceDefault.Medium">
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ </style>
+
+ <style name="Theme.SystemUI.Dialog.Cast">
+ <item name="android:textAppearanceMedium">@style/TextAppearance.CastItem</item>
+ </style>
+ <!-- ************************************************************************************* -->
<style name="Widget" />
<style name="Widget.Dialog" />
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index 65412066651e..5953611b454a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -54,6 +54,7 @@ data class KeyguardFaceListenModel(
val bouncerFullyShown: Boolean,
val faceAuthenticated: Boolean,
val faceDisabled: Boolean,
+ val goingToSleep: Boolean,
val keyguardAwake: Boolean,
val keyguardGoingAway: Boolean,
val listeningForFaceAssistant: Boolean,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index dd2664dacf49..1383635a46d1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -628,7 +628,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
*/
public void setKeyguardGoingAway(boolean goingAway) {
mKeyguardGoingAway = goingAway;
- updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+ // This is set specifically to stop face authentication from running.
+ updateBiometricListeningState(BIOMETRIC_ACTION_STOP);
}
/**
@@ -1746,7 +1747,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
cb.onFinishedGoingToSleep(arg1);
}
}
- updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+ // This is set specifically to stop face authentication from running.
+ updateBiometricListeningState(BIOMETRIC_ACTION_STOP);
}
private void handleScreenTurnedOff() {
@@ -1764,7 +1766,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
cb.onDreamingStateChanged(mIsDreaming);
}
}
- updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+ if (mIsDreaming) {
+ updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
+ updateFaceListeningState(BIOMETRIC_ACTION_STOP);
+ } else {
+ updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
+ }
}
private void handleUserInfoChanged(int userId) {
@@ -2477,9 +2484,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
boolean strongAuthAllowsScanning = (!isEncryptedOrTimedOut || canBypass
&& !mBouncerFullyShown);
- // If the device supports face detection (without authentication), allow it to happen
- // if the device is in lockdown mode. Otherwise, prevent scanning.
+ // If the device supports face detection (without authentication) and bypass is enabled,
+ // allow face scanning to happen if the device is in lockdown mode.
+ // Otherwise, prevent scanning.
final boolean supportsDetectOnly = !mFaceSensorProperties.isEmpty()
+ && canBypass
&& mFaceSensorProperties.get(0).supportsFaceDetection;
if (isLockDown && !supportsDetectOnly) {
strongAuthAllowsScanning = false;
@@ -2494,8 +2503,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
// Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
// instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
final boolean shouldListen =
- (mBouncerFullyShown || mAuthInterruptActive || mOccludingAppRequestingFace
- || awakeKeyguard || shouldListenForFaceAssistant
+ (mBouncerFullyShown && !mGoingToSleep
+ || mAuthInterruptActive
+ || mOccludingAppRequestingFace
+ || awakeKeyguard
+ || shouldListenForFaceAssistant
|| mAuthController.isUdfpsFingerDown())
&& !mSwitchingUser && !faceDisabledForUser && becauseCannotSkipBouncer
&& !mKeyguardGoingAway && biometricEnabledForUser && !mLockIconPressed
@@ -2517,6 +2529,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mBouncerFullyShown,
faceAuthenticated,
faceDisabledForUser,
+ mGoingToSleep,
awakeKeyguard,
mKeyguardGoingAway,
shouldListenForFaceAssistant,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
index f3a603f832fa..59c658fa43d2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
@@ -23,6 +23,7 @@ import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.view.accessibility.AccessibilityManager;
+import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.OvershootInterpolator;
@@ -40,13 +41,15 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable {
private static final long CHECKMARK_ANIMATION_DELAY_MS = 200L;
private static final long CHECKMARK_ANIMATION_DURATION_MS = 300L;
- private static final long FILL_COLOR_ANIMATION_DURATION_MS = 200L;
+ private static final long FILL_COLOR_ANIMATION_DURATION_MS = 350L;
private static final long PROGRESS_ANIMATION_DURATION_MS = 400L;
private static final float STROKE_WIDTH_DP = 12f;
+ private static final Interpolator DEACCEL = new DecelerateInterpolator();
private final float mStrokeWidthPx;
@ColorInt private final int mProgressColor;
@ColorInt private final int mHelpColor;
+ @ColorInt private final int mOnFirstBucketFailedColor;
@NonNull private final Drawable mCheckmarkDrawable;
@NonNull private final Interpolator mCheckmarkInterpolator;
@NonNull private final Paint mBackgroundPaint;
@@ -64,6 +67,9 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable {
@Nullable private ValueAnimator mFillColorAnimator;
@NonNull private final ValueAnimator.AnimatorUpdateListener mFillColorUpdateListener;
+ @Nullable private ValueAnimator mBackgroundColorAnimator;
+ @NonNull private final ValueAnimator.AnimatorUpdateListener mBackgroundColorUpdateListener;
+
private boolean mComplete = false;
private float mCheckmarkScale = 0f;
@Nullable private ValueAnimator mCheckmarkAnimator;
@@ -76,8 +82,10 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable {
final boolean isAccessbilityEnabled = am.isTouchExplorationEnabled();
if (!isAccessbilityEnabled) {
mHelpColor = context.getColor(R.color.udfps_enroll_progress_help);
+ mOnFirstBucketFailedColor = context.getColor(R.color.udfps_moving_target_fill_error);
} else {
mHelpColor = context.getColor(R.color.udfps_enroll_progress_help_with_talkback);
+ mOnFirstBucketFailedColor = mHelpColor;
}
mCheckmarkDrawable = context.getDrawable(R.drawable.udfps_enroll_checkmark);
mCheckmarkDrawable.mutate();
@@ -112,6 +120,11 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable {
mCheckmarkScale = (float) animation.getAnimatedValue();
invalidateSelf();
};
+
+ mBackgroundColorUpdateListener = animation -> {
+ mBackgroundPaint.setColor((int) animation.getAnimatedValue());
+ invalidateSelf();
+ };
}
void onEnrollmentProgress(int remaining, int totalSteps) {
@@ -163,19 +176,38 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable {
}
}
+ private void animateBackgroundColor() {
+ if (mBackgroundColorAnimator != null && mBackgroundColorAnimator.isRunning()) {
+ mBackgroundColorAnimator.end();
+ }
+ mBackgroundColorAnimator = ValueAnimator.ofArgb(mBackgroundPaint.getColor(),
+ mOnFirstBucketFailedColor);
+ mBackgroundColorAnimator.setDuration(FILL_COLOR_ANIMATION_DURATION_MS);
+ mBackgroundColorAnimator.setRepeatCount(1);
+ mBackgroundColorAnimator.setRepeatMode(ValueAnimator.REVERSE);
+ mBackgroundColorAnimator.setInterpolator(DEACCEL);
+ mBackgroundColorAnimator.addUpdateListener(mBackgroundColorUpdateListener);
+ mBackgroundColorAnimator.start();
+ }
+
private void updateFillColor(boolean showingHelp) {
- if (mShowingHelp == showingHelp) {
+ if (!mAfterFirstTouch && showingHelp) {
+ // If we are on the first touch, animate the background color
+ // instead of the progress color.
+ animateBackgroundColor();
return;
}
- mShowingHelp = showingHelp;
if (mFillColorAnimator != null && mFillColorAnimator.isRunning()) {
- mFillColorAnimator.cancel();
+ mFillColorAnimator.end();
}
@ColorInt final int targetColor = showingHelp ? mHelpColor : mProgressColor;
mFillColorAnimator = ValueAnimator.ofArgb(mFillPaint.getColor(), targetColor);
mFillColorAnimator.setDuration(FILL_COLOR_ANIMATION_DURATION_MS);
+ mFillColorAnimator.setRepeatCount(1);
+ mFillColorAnimator.setRepeatMode(ValueAnimator.REVERSE);
+ mFillColorAnimator.setInterpolator(DEACCEL);
mFillColorAnimator.addUpdateListener(mFillColorUpdateListener);
mFillColorAnimator.start();
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 6d727b4cf966..b172e92871ce 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -65,6 +65,7 @@ import android.safetycenter.SafetyCenterManager;
import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
import android.telecom.TelecomManager;
+import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.view.CrossWindowBlurListeners;
@@ -452,6 +453,12 @@ public class FrameworkServicesModule {
@Provides
@Singleton
+ static CarrierConfigManager provideCarrierConfigManager(Context context) {
+ return context.getSystemService(CarrierConfigManager.class);
+ }
+
+ @Provides
+ @Singleton
static WindowManager provideWindowManager(Context context) {
return context.getSystemService(WindowManager.class);
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 9356b16806f1..c9a61a8a09df 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -112,6 +112,10 @@ public class Flags {
public static final ResourceBooleanFlag QS_USER_DETAIL_SHORTCUT =
new ResourceBooleanFlag(503, R.bool.flag_lockscreen_qs_user_detail_shortcut);
+ /**
+ * @deprecated Not needed anymore
+ */
+ @Deprecated
public static final BooleanFlag NEW_FOOTER = new BooleanFlag(504, true);
public static final BooleanFlag NEW_HEADER = new BooleanFlag(505, false);
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
index 96ae646ac7f9..290bf0d0734c 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -41,26 +41,23 @@ import com.android.systemui.statusbar.policy.KeyguardStateController;
import javax.inject.Inject;
-import dagger.Lazy;
-
public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks {
private final Context mContext;
- private final Lazy<GlobalActionsDialogLite> mGlobalActionsDialogLazy;
private final KeyguardStateController mKeyguardStateController;
private final DeviceProvisionedController mDeviceProvisionedController;
private final BlurUtils mBlurUtils;
private final CommandQueue mCommandQueue;
- private GlobalActionsDialogLite mGlobalActionsDialog;
+ private final GlobalActionsDialogLite mGlobalActionsDialog;
private boolean mDisabled;
@Inject
public GlobalActionsImpl(Context context, CommandQueue commandQueue,
- Lazy<GlobalActionsDialogLite> globalActionsDialogLazy, BlurUtils blurUtils,
+ GlobalActionsDialogLite globalActionsDialog, BlurUtils blurUtils,
KeyguardStateController keyguardStateController,
DeviceProvisionedController deviceProvisionedController) {
mContext = context;
- mGlobalActionsDialogLazy = globalActionsDialogLazy;
+ mGlobalActionsDialog = globalActionsDialog;
mKeyguardStateController = keyguardStateController;
mDeviceProvisionedController = deviceProvisionedController;
mCommandQueue = commandQueue;
@@ -71,16 +68,12 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks
@Override
public void destroy() {
mCommandQueue.removeCallback(this);
- if (mGlobalActionsDialog != null) {
- mGlobalActionsDialog.destroy();
- mGlobalActionsDialog = null;
- }
+ mGlobalActionsDialog.destroy();
}
@Override
public void showGlobalActions(GlobalActionsManager manager) {
if (mDisabled) return;
- mGlobalActionsDialog = mGlobalActionsDialogLazy.get();
mGlobalActionsDialog.showOrHideDialog(mKeyguardStateController.isShowing(),
mDeviceProvisionedController.isDeviceProvisioned(), null /* view */);
}
@@ -189,7 +182,7 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks
final boolean disabled = (state2 & DISABLE2_GLOBAL_ACTIONS) != 0;
if (displayId != mContext.getDisplayId() || disabled == mDisabled) return;
mDisabled = disabled;
- if (disabled && mGlobalActionsDialog != null) {
+ if (disabled) {
mGlobalActionsDialog.dismissDialog();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index b93239ba7197..d674b2b85db4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -690,6 +690,11 @@ class MediaCarouselController @Inject constructor(
startDelay: Long = 0
) {
desiredHostState?.let {
+ if (this.desiredLocation != desiredLocation) {
+ // Only log an event when location changes
+ logger.logCarouselPosition(desiredLocation)
+ }
+
// This is a hosting view, let's remeasure our players
this.desiredLocation = desiredLocation
this.desiredHostState = it
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 5ead375a1084..3a727ba7b70c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -333,7 +333,7 @@ public class MediaControlPanel {
mMediaViewHolder.getPlayer().setOnClickListener(v -> {
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return;
if (mMediaViewController.isGutsVisible()) return;
-
+ mLogger.logTapContentView(mUid, mPackageName, mInstanceId);
logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT);
mActivityStarter.postStartActivityDismissingKeyguard(clickIntent,
buildLaunchAnimatorController(mMediaViewHolder.getPlayer()));
@@ -660,38 +660,42 @@ public class MediaControlPanel {
final ImageButton button, MediaAction mediaAction, ConstraintSet collapsedSet,
ConstraintSet expandedSet, boolean showInCompact) {
- animHandler.unregisterAll();
if (mediaAction != null) {
- final Drawable icon = mediaAction.getIcon();
- button.setImageDrawable(icon);
- button.setContentDescription(mediaAction.getContentDescription());
- final Drawable bgDrawable = mediaAction.getBackground();
- button.setBackground(bgDrawable);
-
- animHandler.tryRegister(icon);
- animHandler.tryRegister(bgDrawable);
-
- Runnable action = mediaAction.getAction();
- if (action == null) {
- button.setEnabled(false);
- } else {
- button.setEnabled(true);
- button.setOnClickListener(v -> {
- if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- mLogger.logTapAction(button.getId(), mUid, mPackageName, mInstanceId);
- logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT);
- action.run();
-
- if (icon instanceof Animatable) {
- ((Animatable) icon).start();
- }
- if (bgDrawable instanceof Animatable) {
- ((Animatable) bgDrawable).start();
+ if (animHandler.updateRebindId(mediaAction.getRebindId())) {
+ animHandler.unregisterAll();
+
+ final Drawable icon = mediaAction.getIcon();
+ button.setImageDrawable(icon);
+ button.setContentDescription(mediaAction.getContentDescription());
+ final Drawable bgDrawable = mediaAction.getBackground();
+ button.setBackground(bgDrawable);
+
+ animHandler.tryRegister(icon);
+ animHandler.tryRegister(bgDrawable);
+
+ Runnable action = mediaAction.getAction();
+ if (action == null) {
+ button.setEnabled(false);
+ } else {
+ button.setEnabled(true);
+ button.setOnClickListener(v -> {
+ if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ mLogger.logTapAction(button.getId(), mUid, mPackageName, mInstanceId);
+ logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT);
+ action.run();
+
+ if (icon instanceof Animatable) {
+ ((Animatable) icon).start();
+ }
+ if (bgDrawable instanceof Animatable) {
+ ((Animatable) bgDrawable).start();
+ }
}
- }
- });
+ });
+ }
}
} else {
+ animHandler.unregisterAll();
button.setImageDrawable(null);
button.setContentDescription(null);
button.setEnabled(false);
@@ -702,9 +706,29 @@ public class MediaControlPanel {
setVisibleAndAlpha(expandedSet, button.getId(), mediaAction != null);
}
+ // AnimationBindHandler is responsible for tracking the bound animation state and preventing
+ // jank and conflicts due to media notifications arriving at any time during an animation. It
+ // does this in two parts.
+ // - Exit animations fired as a result of user input are tracked. When these are running, any
+ // bind actions are delayed until the animation completes (and then fired in sequence).
+ // - Continuous animations are tracked using their rebind id. Later calls using the same
+ // rebind id will be totally ignored to prevent the continuous animation from restarting.
private static class AnimationBindHandler extends Animatable2.AnimationCallback {
private ArrayList<Runnable> mOnAnimationsComplete = new ArrayList<>();
private ArrayList<Animatable2> mRegistrations = new ArrayList<>();
+ private Integer mRebindId = null;
+
+ // This check prevents rebinding to the action button if the identifier has not changed. A
+ // null value is always considered to be changed. This is used to prevent the connecting
+ // animation from rebinding (and restarting) if multiple buffer PlaybackStates are pushed by
+ // an application in a row.
+ public boolean updateRebindId(Integer rebindId) {
+ if (mRebindId == null || rebindId == null || !mRebindId.equals(rebindId)) {
+ mRebindId = rebindId;
+ return true;
+ }
+ return false;
+ }
public void tryRegister(Drawable drawable) {
if (drawable instanceof Animatable2) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
index a4d2f7bc96c4..bc8cca55154d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
@@ -184,7 +184,12 @@ data class MediaAction(
val icon: Drawable?,
val action: Runnable?,
val contentDescription: CharSequence?,
- val background: Drawable?
+ val background: Drawable?,
+
+ // Rebind Id is used to detect identical rebinds and ignore them. It is intended
+ // to prevent continuously looping animations from restarting due to the arrival
+ // of repeated media notifications that are visually identical.
+ val rebindId: Int? = null
)
/** State of the media device. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 908aef41034e..57c93bae3bbf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -30,6 +30,7 @@ import android.content.IntentFilter
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.ImageDecoder
+import android.graphics.drawable.Animatable
import android.graphics.drawable.Icon
import android.media.MediaDescription
import android.media.MediaMetadata
@@ -57,6 +58,7 @@ import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
+import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState
import com.android.systemui.statusbar.notification.row.HybridGroupManager
import com.android.systemui.tuner.TunerService
import com.android.systemui.util.Assert
@@ -777,7 +779,20 @@ class MediaDataManager(
val actions = MediaButton()
controller.playbackState?.let { state ->
// First, check for standard actions
- actions.playOrPause = if (isPlayingState(state.state)) {
+ actions.playOrPause = if (isConnectingState(state.state)) {
+ // Spinner needs to be animating to render anything. Start it here.
+ val drawable = context.getDrawable(
+ com.android.internal.R.drawable.progress_small_material)
+ (drawable as Animatable).start()
+ MediaAction(
+ drawable,
+ null, // no action to perform when clicked
+ context.getString(R.string.controls_media_button_connecting),
+ context.getDrawable(R.drawable.ic_media_connecting_container),
+ // Specify a rebind id to prevent the spinner from restarting on later binds.
+ com.android.internal.R.drawable.progress_small_material
+ )
+ } else if (isPlayingState(state.state)) {
getStandardAction(controller, state.actions, PlaybackState.ACTION_PAUSE)
} else {
getStandardAction(controller, state.actions, PlaybackState.ACTION_PLAY)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
index 824a6fd9d96e..d6231911244f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
@@ -164,7 +164,7 @@ class MediaDeviceManager @Inject constructor(
}
// A device that is not yet connected but is expected to connect imminently. Because it's
// expected to connect imminently, it should be displayed as the current device.
- private var aboutToConnectDeviceOverride: MediaDeviceData? = null
+ private var aboutToConnectDeviceOverride: AboutToConnectDevice? = null
@AnyThread
fun start() = bgExecutor.execute {
@@ -222,22 +222,34 @@ class MediaDeviceManager @Inject constructor(
}
}
- override fun onAboutToConnectDeviceChanged(deviceName: String?, deviceIcon: Drawable?) {
- aboutToConnectDeviceOverride = if (deviceName == null || deviceIcon == null) {
- null
- } else {
- MediaDeviceData(enabled = true, deviceIcon, deviceName)
- }
+ override fun onAboutToConnectDeviceAdded(
+ deviceAddress: String,
+ deviceName: String,
+ deviceIcon: Drawable?
+ ) {
+ aboutToConnectDeviceOverride = AboutToConnectDevice(
+ fullMediaDevice = localMediaManager.getMediaDeviceById(deviceAddress),
+ backupMediaDeviceData = MediaDeviceData(enabled = true, deviceIcon, deviceName)
+ )
+ updateCurrent()
+ }
+
+ override fun onAboutToConnectDeviceRemoved() {
+ aboutToConnectDeviceOverride = null
updateCurrent()
}
@WorkerThread
private fun updateCurrent() {
- if (aboutToConnectDeviceOverride != null) {
- current = aboutToConnectDeviceOverride
- return
+ val aboutToConnect = aboutToConnectDeviceOverride
+ if (aboutToConnect != null &&
+ aboutToConnect.fullMediaDevice == null &&
+ aboutToConnect.backupMediaDeviceData != null) {
+ // Only use [backupMediaDeviceData] when we don't have [fullMediaDevice].
+ current = aboutToConnect.backupMediaDeviceData
+ return
}
- val device = localMediaManager.currentConnectedDevice
+ val device = aboutToConnect?.fullMediaDevice ?: localMediaManager.currentConnectedDevice
val route = controller?.let { mr2manager.getRoutingSessionForMediaController(it) }
// If we have a controller but get a null route, then don't trust the device
@@ -247,3 +259,17 @@ class MediaDeviceManager @Inject constructor(
}
}
}
+
+/**
+ * A class storing information for the about-to-connect device. See
+ * [LocalMediaManager.DeviceCallback.onAboutToConnectDeviceAdded] for more information.
+ *
+ * @property fullMediaDevice a full-fledged [MediaDevice] object representing the device. If
+ * non-null, prefer using [fullMediaDevice] over [backupMediaDeviceData].
+ * @property backupMediaDeviceData a backup [MediaDeviceData] object containing the minimum
+ * information required to display the device. Only use if [fullMediaDevice] is null.
+ */
+private data class AboutToConnectDevice(
+ val fullMediaDevice: MediaDevice? = null,
+ val backupMediaDeviceData: MediaDeviceData? = null
+)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index 3d9f933b3a05..30771c728e86 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -1146,7 +1146,10 @@ private val EMPTY_RECT = Rect()
@Retention(AnnotationRetention.SOURCE)
private annotation class TransformationType
-@IntDef(prefix = ["LOCATION_"], value = [MediaHierarchyManager.LOCATION_QS,
- MediaHierarchyManager.LOCATION_QQS, MediaHierarchyManager.LOCATION_LOCKSCREEN])
+@IntDef(prefix = ["LOCATION_"], value = [
+ MediaHierarchyManager.LOCATION_QS,
+ MediaHierarchyManager.LOCATION_QQS,
+ MediaHierarchyManager.LOCATION_LOCKSCREEN,
+ MediaHierarchyManager.LOCATION_DREAM_OVERLAY])
@Retention(AnnotationRetention.SOURCE)
annotation class MediaLocation
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt b/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt
index 862b2797b77c..3eba3b55b7e8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt
@@ -134,6 +134,23 @@ class MediaUiEventLogger @Inject constructor(private val logger: UiEventLogger)
fun logOpenOutputSwitcher(uid: Int, packageName: String, instanceId: InstanceId) {
logger.logWithInstanceId(MediaUiEvent.OPEN_OUTPUT_SWITCHER, uid, packageName, instanceId)
}
+
+ fun logTapContentView(uid: Int, packageName: String, instanceId: InstanceId) {
+ logger.logWithInstanceId(MediaUiEvent.MEDIA_TAP_CONTENT_VIEW, uid, packageName, instanceId)
+ }
+
+ fun logCarouselPosition(@MediaLocation location: Int) {
+ val event = when (location) {
+ MediaHierarchyManager.LOCATION_QQS -> MediaUiEvent.MEDIA_CAROUSEL_LOCATION_QQS
+ MediaHierarchyManager.LOCATION_QS -> MediaUiEvent.MEDIA_CAROUSEL_LOCATION_QS
+ MediaHierarchyManager.LOCATION_LOCKSCREEN ->
+ MediaUiEvent.MEDIA_CAROUSEL_LOCATION_LOCKSCREEN
+ MediaHierarchyManager.LOCATION_DREAM_OVERLAY ->
+ MediaUiEvent.MEDIA_CAROUSEL_LOCATION_DREAM
+ else -> throw IllegalArgumentException("Unknown media carousel location $location")
+ }
+ logger.log(event)
+ }
}
enum class MediaUiEvent(val metricId: Int) : UiEventLogger.UiEventEnum {
@@ -161,7 +178,7 @@ enum class MediaUiEvent(val metricId: Int) : UiEventLogger.UiEventEnum {
@UiEvent(doc = "An existing active media control was converted into resumable media")
ACTIVE_TO_RESUME(1014),
- @UiEvent(doc = "Media timed out")
+ @UiEvent(doc = "A media control timed out")
MEDIA_TIMEOUT(1015),
@UiEvent(doc = "A media control was removed from the carousel")
@@ -173,35 +190,50 @@ enum class MediaUiEvent(val metricId: Int) : UiEventLogger.UiEventEnum {
@UiEvent(doc = "The user swiped away the media carousel")
DISMISS_SWIPE(1018),
- @UiEvent(doc = "The user opened the long press menu")
+ @UiEvent(doc = "The user long pressed on a media control")
OPEN_LONG_PRESS(1019),
- @UiEvent(doc = "The user dismissed via long press menu")
+ @UiEvent(doc = "The user dismissed a media control via its long press menu")
DISMISS_LONG_PRESS(1020),
- @UiEvent(doc = "The user opened settings from long press menu")
+ @UiEvent(doc = "The user opened media settings from a media control's long press menu")
OPEN_SETTINGS_LONG_PRESS(1021),
- @UiEvent(doc = "The user opened settings from the carousel")
+ @UiEvent(doc = "The user opened media settings from the media carousel")
OPEN_SETTINGS_CAROUSEL(1022),
- @UiEvent(doc = "The play/pause button was tapped")
+ @UiEvent(doc = "The play/pause button on a media control was tapped")
TAP_ACTION_PLAY_PAUSE(1023),
- @UiEvent(doc = "The previous button was tapped")
+ @UiEvent(doc = "The previous button on a media control was tapped")
TAP_ACTION_PREV(1024),
- @UiEvent(doc = "The next button was tapped")
+ @UiEvent(doc = "The next button on a media control was tapped")
TAP_ACTION_NEXT(1025),
- @UiEvent(doc = "A custom or generic action button was tapped")
+ @UiEvent(doc = "A custom or generic action button on a media control was tapped")
TAP_ACTION_OTHER(1026),
- @UiEvent(doc = "The user seeked using the seekbar")
+ @UiEvent(doc = "The user seeked on a media control using the seekbar")
ACTION_SEEK(1027),
@UiEvent(doc = "The user opened the output switcher from a media control")
- OPEN_OUTPUT_SWITCHER(1028);
+ OPEN_OUTPUT_SWITCHER(1028),
+
+ @UiEvent(doc = "The user tapped on a media control view")
+ MEDIA_TAP_CONTENT_VIEW(1036),
+
+ @UiEvent(doc = "The media carousel moved to QQS")
+ MEDIA_CAROUSEL_LOCATION_QQS(1037),
+
+ @UiEvent(doc = "THe media carousel moved to QS")
+ MEDIA_CAROUSEL_LOCATION_QS(1038),
+
+ @UiEvent(doc = "The media carousel moved to the lockscreen")
+ MEDIA_CAROUSEL_LOCATION_LOCKSCREEN(1039),
+
+ @UiEvent(doc = "The media carousel moved to the dream state")
+ MEDIA_CAROUSEL_LOCATION_DREAM(1040);
override fun getId() = metricId
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 807f0f1bb0ba..ec2a950051b7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -721,7 +721,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
boolean isVolumeControlEnabled(@NonNull MediaDevice device) {
return isPlayBackInfoLocal()
- || mLocalMediaManager.isMediaSessionAvailableForVolumeControl();
+ || device.getDeviceType() != MediaDevice.MediaDeviceType.TYPE_CAST_GROUP_DEVICE;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManager.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManager.kt
index 22bc5572f5a5..2783532aff97 100644
--- a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManager.kt
@@ -52,7 +52,9 @@ class MediaMuteAwaitConnectionManager constructor(
// There should only be one device that's mutedUntilConnection at a time, so we can
// safely override any previous value.
currentMutedDevice = device
- localMediaManager.dispatchAboutToConnectDeviceChanged(device.name, device.getIcon())
+ localMediaManager.dispatchAboutToConnectDeviceAdded(
+ device.address, device.name, device.getIcon()
+ )
}
}
@@ -63,7 +65,7 @@ class MediaMuteAwaitConnectionManager constructor(
) {
if (currentMutedDevice == device && USAGE_MEDIA in mutedUsages) {
currentMutedDevice = null
- localMediaManager.dispatchAboutToConnectDeviceChanged(null, null)
+ localMediaManager.dispatchAboutToConnectDeviceRemoved()
}
}
}
@@ -76,8 +78,8 @@ class MediaMuteAwaitConnectionManager constructor(
val currentDevice = audioManager.mutingExpectedDevice
if (currentDevice != null) {
currentMutedDevice = currentDevice
- localMediaManager.dispatchAboutToConnectDeviceChanged(
- currentDevice.name, currentDevice.getIcon()
+ localMediaManager.dispatchAboutToConnectDeviceAdded(
+ currentDevice.address, currentDevice.name, currentDevice.getIcon()
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index f5abe28914c3..40265a367a06 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -143,6 +143,7 @@ public class NavigationBarView extends FrameLayout {
private boolean mDeadZoneConsuming = false;
private final NavigationBarTransitions mBarTransitions;
private final OverviewProxyService mOverviewProxyService;
+ @Nullable
private AutoHideController mAutoHideController;
// performs manual animation in sync with layout transitions
@@ -316,7 +317,7 @@ public class NavigationBarView extends FrameLayout {
};
private final Consumer<Boolean> mNavbarOverlayVisibilityChangeCallback = (visible) -> {
- if (visible) {
+ if (visible && mAutoHideController != null) {
mAutoHideController.touchAutoHide();
}
notifyActiveTouchRegions();
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
index 03652412c129..e5dc0ec54676 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
@@ -360,9 +360,7 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
.getDimension(R.dimen.navigation_edge_action_drag_threshold);
mSwipeProgressThreshold = context.getResources()
.getDimension(R.dimen.navigation_edge_action_progress_threshold);
- if (mBackAnimation != null) {
- mBackAnimation.setSwipeThresholds(mSwipeTriggerThreshold, mSwipeProgressThreshold);
- }
+ initializeBackAnimation();
setVisibility(GONE);
Executor backgroundExecutor = Dependency.get(Dependency.BACKGROUND_EXECUTOR);
@@ -391,6 +389,13 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
public void setBackAnimation(BackAnimation backAnimation) {
mBackAnimation = backAnimation;
+ initializeBackAnimation();
+ }
+
+ private void initializeBackAnimation() {
+ if (mBackAnimation != null) {
+ mBackAnimation.setSwipeThresholds(mSwipeTriggerThreshold, mSwipeProgressThreshold);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index c01d6dcd7d64..c6c9aca0b161 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -466,7 +466,7 @@ public class PeopleSpaceUtils {
}
}
} catch (SQLException e) {
- Log.e(TAG, "Failed to query contact: " + e);
+ Log.e(TAG, "Failed to query contact", e);
} finally {
if (cursor != null) {
cursor.close();
@@ -527,7 +527,7 @@ public class PeopleSpaceUtils {
lookupKeysWithBirthdaysToday.add(lookupKey);
}
} catch (SQLException e) {
- Log.e(TAG, "Failed to query birthdays: " + e);
+ Log.e(TAG, "Failed to query birthdays", e);
} finally {
if (cursor != null) {
cursor.close();
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
index f6e1cd49eb40..1a7bd8cb6cf9 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
@@ -275,7 +275,7 @@ public class PeopleSpaceWidgetManager {
updateSingleConversationWidgets(widgetIds);
}
} catch (Exception e) {
- Log.e(TAG, "Exception: " + e);
+ Log.e(TAG, "failed to update widgets", e);
}
}
@@ -348,7 +348,7 @@ public class PeopleSpaceWidgetManager {
try {
return getTileForExistingWidgetThrowing(appWidgetId);
} catch (Exception e) {
- Log.e(TAG, "Failed to retrieve conversation for tile: " + e);
+ Log.e(TAG, "failed to retrieve tile for widget ID " + appWidgetId, e);
return null;
}
}
@@ -423,7 +423,7 @@ public class PeopleSpaceWidgetManager {
// Add current state.
return getTileWithCurrentState(storedTile.build(), ACTION_BOOT_COMPLETED);
} catch (RemoteException e) {
- Log.e(TAG, "Could not retrieve data: " + e);
+ Log.e(TAG, "getTileFromPersistentStorage failing", e);
return null;
}
}
@@ -441,12 +441,16 @@ public class PeopleSpaceWidgetManager {
Log.d(TAG, "Notification removed, key: " + sbn.getKey());
}
}
+ if (DEBUG) Log.d(TAG, "Fetching notifications");
+ Collection<NotificationEntry> notifications = mNotifCollection.getAllNotifs();
mBgExecutor.execute(
- () -> updateWidgetsWithNotificationChangedInBackground(sbn, notificationAction));
+ () -> updateWidgetsWithNotificationChangedInBackground(
+ sbn, notificationAction, notifications));
}
private void updateWidgetsWithNotificationChangedInBackground(StatusBarNotification sbn,
- PeopleSpaceUtils.NotificationAction action) {
+ PeopleSpaceUtils.NotificationAction action,
+ Collection<NotificationEntry> notifications) {
try {
PeopleTileKey key = new PeopleTileKey(
sbn.getShortcutId(), sbn.getUser().getIdentifier(), sbn.getPackageName());
@@ -469,23 +473,23 @@ public class PeopleSpaceWidgetManager {
Log.d(TAG, "Widgets by URI to be updated:" + tilesUpdatedByUri.toString());
}
tilesUpdated.addAll(tilesUpdatedByUri);
- updateWidgetIdsBasedOnNotifications(tilesUpdated);
+ updateWidgetIdsBasedOnNotifications(tilesUpdated, notifications);
}
} catch (Exception e) {
- Log.e(TAG, "Throwing exception: " + e);
+ Log.e(TAG, "updateWidgetsWithNotificationChangedInBackground failing", e);
}
}
/** Updates {@code widgetIdsToUpdate} with {@code action}. */
- private void updateWidgetIdsBasedOnNotifications(Set<String> widgetIdsToUpdate) {
+ private void updateWidgetIdsBasedOnNotifications(Set<String> widgetIdsToUpdate,
+ Collection<NotificationEntry> ungroupedNotifications) {
if (widgetIdsToUpdate.isEmpty()) {
if (DEBUG) Log.d(TAG, "No widgets to update, returning.");
return;
}
try {
- if (DEBUG) Log.d(TAG, "Fetching grouped notifications");
Map<PeopleTileKey, Set<NotificationEntry>> groupedNotifications =
- getGroupedConversationNotifications();
+ groupConversationNotifications(ungroupedNotifications);
widgetIdsToUpdate
.stream()
@@ -495,7 +499,7 @@ public class PeopleSpaceWidgetManager {
id -> getAugmentedTileForExistingWidget(id, groupedNotifications)))
.forEach((id, tile) -> updateAppWidgetOptionsAndViewOptional(id, tile));
} catch (Exception e) {
- Log.e(TAG, "Exception updating widgets: " + e);
+ Log.e(TAG, "updateWidgetIdsBasedOnNotifications failing", e);
}
}
@@ -510,7 +514,7 @@ public class PeopleSpaceWidgetManager {
"Augmenting tile from NotificationEntryManager widget: " + key.toString());
}
Map<PeopleTileKey, Set<NotificationEntry>> notifications =
- getGroupedConversationNotifications();
+ groupConversationNotifications(mNotifCollection.getAllNotifs());
String contactUri = null;
if (tile.getContactUri() != null) {
contactUri = tile.getContactUri().toString();
@@ -518,9 +522,10 @@ public class PeopleSpaceWidgetManager {
return augmentTileFromNotifications(tile, key, contactUri, notifications, appWidgetId);
}
- /** Returns active and pending notifications grouped by {@link PeopleTileKey}. */
- public Map<PeopleTileKey, Set<NotificationEntry>> getGroupedConversationNotifications() {
- Collection<NotificationEntry> notifications = mNotifCollection.getAllNotifs();
+ /** Groups active and pending notifications grouped by {@link PeopleTileKey}. */
+ public Map<PeopleTileKey, Set<NotificationEntry>> groupConversationNotifications(
+ Collection<NotificationEntry> notifications
+ ) {
if (DEBUG) Log.d(TAG, "Number of total notifications: " + notifications.size());
Map<PeopleTileKey, Set<NotificationEntry>> groupedNotifications =
notifications
@@ -846,7 +851,7 @@ public class PeopleSpaceWidgetManager {
Collections.singletonList(tile.getId()),
tile.getUserHandle(), LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS);
} catch (Exception e) {
- Log.w(TAG, "Exception caching shortcut:" + e);
+ Log.w(TAG, "failed to cache shortcut", e);
}
PeopleSpaceTile finalTile = tile;
mBgExecutor.execute(
@@ -954,7 +959,7 @@ public class PeopleSpaceWidgetManager {
UserHandle.of(key.getUserId()),
LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS);
} catch (Exception e) {
- Log.d(TAG, "Exception uncaching shortcut:" + e);
+ Log.d(TAG, "failed to uncache shortcut", e);
}
}
@@ -1037,7 +1042,7 @@ public class PeopleSpaceWidgetManager {
packageName, userHandle.getIdentifier(), shortcutId);
tile = PeopleSpaceUtils.getTile(channel, mLauncherApps);
} catch (Exception e) {
- Log.w(TAG, "Exception getting tiles: " + e);
+ Log.w(TAG, "failed to get conversation or tile", e);
return null;
}
if (tile == null) {
@@ -1086,7 +1091,7 @@ public class PeopleSpaceWidgetManager {
}
} catch (PackageManager.NameNotFoundException e) {
// Delete data for uninstalled widgets.
- Log.e(TAG, "Package no longer found for tile: " + e);
+ Log.e(TAG, "package no longer found for tile", e);
JobScheduler jobScheduler = mContext.getSystemService(JobScheduler.class);
if (jobScheduler != null
&& jobScheduler.getPendingJob(PeopleBackupFollowUpJob.JOB_ID) != null) {
@@ -1296,7 +1301,7 @@ public class PeopleSpaceWidgetManager {
try {
editor.putString(newId, (String) entry.getValue());
} catch (Exception e) {
- Log.e(TAG, "Malformed entry value: " + entry.getValue());
+ Log.e(TAG, "malformed entry value: " + entry.getValue(), e);
}
editor.remove(key);
break;
@@ -1306,7 +1311,7 @@ public class PeopleSpaceWidgetManager {
try {
oldWidgetIds = (Set<String>) entry.getValue();
} catch (Exception e) {
- Log.e(TAG, "Malformed entry value: " + entry.getValue());
+ Log.e(TAG, "malformed entry value: " + entry.getValue(), e);
editor.remove(key);
break;
}
@@ -1337,7 +1342,7 @@ public class PeopleSpaceWidgetManager {
try {
oldWidgetIds = (Set<String>) entry.getValue();
} catch (Exception e) {
- Log.e(TAG, "Malformed entry value: " + entry.getValue());
+ Log.e(TAG, "malformed entry value: " + entry.getValue(), e);
followUpEditor.remove(key);
continue;
}
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index 2435497193e4..3e00a5f74d8f 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -18,6 +18,7 @@ package com.android.systemui.power;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
+import android.app.Dialog;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
@@ -60,20 +61,25 @@ import com.android.settingslib.fuelgauge.BatterySaverUtils;
import com.android.settingslib.utils.PowerUtil;
import com.android.systemui.R;
import com.android.systemui.SystemUIApplication;
+import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.NotificationChannels;
import com.android.systemui.volume.Events;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
import java.text.NumberFormat;
import java.util.Locale;
import java.util.Objects;
import javax.inject.Inject;
+import dagger.Lazy;
+
/**
*/
@SysUISingleton
@@ -164,11 +170,15 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
private ActivityStarter mActivityStarter;
private final BroadcastSender mBroadcastSender;
+ private final Lazy<BatteryController> mBatteryControllerLazy;
+ private final DialogLaunchAnimator mDialogLaunchAnimator;
+
/**
*/
@Inject
public PowerNotificationWarnings(Context context, ActivityStarter activityStarter,
- BroadcastSender broadcastSender) {
+ BroadcastSender broadcastSender, Lazy<BatteryController> batteryControllerLazy,
+ DialogLaunchAnimator dialogLaunchAnimator) {
mContext = context;
mNoMan = mContext.getSystemService(NotificationManager.class);
mPowerMan = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
@@ -176,6 +186,8 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
mReceiver.init();
mActivityStarter = activityStarter;
mBroadcastSender = broadcastSender;
+ mBatteryControllerLazy = batteryControllerLazy;
+ mDialogLaunchAnimator = dialogLaunchAnimator;
mUseSevereDialog = mContext.getResources().getBoolean(R.bool.config_severe_battery_dialog);
}
@@ -685,8 +697,19 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
}
d.setShowForAllUsers(true);
d.setOnDismissListener((dialog) -> mSaverConfirmation = null);
- d.show();
+ WeakReference<View> ref = mBatteryControllerLazy.get().getLastPowerSaverStartView();
+ if (ref != null && ref.get() != null && ref.get().isAggregatedVisible()) {
+ mDialogLaunchAnimator.showFromView(d, ref.get());
+ } else {
+ d.show();
+ }
mSaverConfirmation = d;
+ mBatteryControllerLazy.get().clearLastPowerSaverStartView();
+ }
+
+ @VisibleForTesting
+ Dialog getSaverConfirmationDialog() {
+ return mSaverConfirmation;
}
private boolean isEnglishLocale() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
index 44ef2b658fe2..3f394e7b5309 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
@@ -23,7 +23,6 @@ import android.provider.Settings
import android.provider.Settings.Global.USER_SWITCHER_ENABLED
import android.view.View
import android.view.ViewGroup
-import android.widget.LinearLayout
import androidx.annotation.VisibleForTesting
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.MetricsLogger
@@ -32,8 +31,6 @@ import com.android.internal.logging.nano.MetricsProto
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.globalactions.GlobalActionsDialogLite
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
@@ -45,7 +42,6 @@ import com.android.systemui.statusbar.phone.SettingsButton
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.UserInfoController
import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener
-import com.android.systemui.util.DualHeightHorizontalLinearLayout
import com.android.systemui.util.ViewController
import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
@@ -74,8 +70,7 @@ internal class FooterActionsController @Inject constructor(
private val uiEventLogger: UiEventLogger,
@Named(PM_LITE_ENABLED) private val showPMLiteButton: Boolean,
private val globalSetting: GlobalSettings,
- private val handler: Handler,
- private val featureFlags: FeatureFlags
+ private val handler: Handler
) : ViewController<FooterActionsView>(view) {
private var globalActionsDialog: GlobalActionsDialogLite? = null
@@ -100,7 +95,9 @@ internal class FooterActionsController @Inject constructor(
view.findViewById(R.id.security_footers_container)
private val powerMenuLite: View = view.findViewById(R.id.pm_lite)
private val multiUserSwitchController = multiUserSwitchControllerFactory.create(view)
- private val securityFootersSeparator = View(context).apply {
+
+ @VisibleForTesting
+ internal val securityFootersSeparator = View(context).apply {
visibility = View.GONE
}
@@ -171,48 +168,30 @@ internal class FooterActionsController @Inject constructor(
}
settingsButton.setOnClickListener(onClickListener)
multiUserSetting.isListening = true
- if (featureFlags.isEnabled(Flags.NEW_FOOTER)) {
- val securityFooter = securityFooterController.view as DualHeightHorizontalLinearLayout
- securityFootersContainer?.addView(securityFooter)
- val separatorWidth = resources.getDimensionPixelSize(R.dimen.new_qs_footer_action_inset)
- securityFootersContainer?.addView(securityFootersSeparator, separatorWidth, 1)
- reformatForNewFooter(securityFooter)
- val fgsFooter = fgsManagerFooterController.view
- securityFootersContainer?.addView(fgsFooter)
- (fgsFooter.layoutParams as LinearLayout.LayoutParams).apply {
- width = 0
- weight = 1f
- }
- val visibilityListener =
- VisibilityChangedDispatcher.OnVisibilityChangedListener { visibility ->
- if (visibility == View.GONE) {
- securityFootersSeparator.visibility = View.GONE
- } else if (securityFooter.visibility == View.VISIBLE &&
- fgsFooter.visibility == View.VISIBLE) {
- securityFootersSeparator.visibility = View.VISIBLE
- } else {
- securityFootersSeparator.visibility = View.GONE
- }
- fgsManagerFooterController
- .setCollapsed(securityFooter.visibility == View.VISIBLE)
+ val securityFooter = securityFooterController.view
+ securityFootersContainer?.addView(securityFooter)
+ val separatorWidth = resources.getDimensionPixelSize(R.dimen.qs_footer_action_inset)
+ securityFootersContainer?.addView(securityFootersSeparator, separatorWidth, 1)
+
+ val fgsFooter = fgsManagerFooterController.view
+ securityFootersContainer?.addView(fgsFooter)
+
+ val visibilityListener =
+ VisibilityChangedDispatcher.OnVisibilityChangedListener { visibility ->
+ if (securityFooter.visibility == View.VISIBLE &&
+ fgsFooter.visibility == View.VISIBLE) {
+ securityFootersSeparator.visibility = View.VISIBLE
+ } else {
+ securityFootersSeparator.visibility = View.GONE
}
- securityFooterController.setOnVisibilityChangedListener(visibilityListener)
- fgsManagerFooterController.setOnVisibilityChangedListener(visibilityListener)
- }
- updateView()
- }
+ fgsManagerFooterController
+ .setCollapsed(securityFooter.visibility == View.VISIBLE)
+ }
+ securityFooterController.setOnVisibilityChangedListener(visibilityListener)
+ fgsManagerFooterController.setOnVisibilityChangedListener(visibilityListener)
- private fun reformatForNewFooter(view: DualHeightHorizontalLinearLayout) {
- // This is only necessary while things are flagged as the view could be attached in two
- // different locations.
- (view.layoutParams as LinearLayout.LayoutParams).apply {
- bottomMargin = 0
- width = 0
- weight = 1f
- marginEnd = resources.getDimensionPixelSize(R.dimen.new_qs_footer_action_inset)
- }
- view.alwaysSingleLine = true
+ updateView()
}
private fun updateView() {
@@ -237,10 +216,9 @@ internal class FooterActionsController @Inject constructor(
} else {
userInfoController.removeCallback(onUserInfoChangedListener)
}
- if (featureFlags.isEnabled(Flags.NEW_FOOTER)) {
- fgsManagerFooterController.setListening(listening)
- securityFooterController.setListening(listening)
- }
+
+ fgsManagerFooterController.setListening(listening)
+ securityFooterController.setListening(listening)
}
fun disable(state2: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 4640205c82f5..56298fa155fa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -81,8 +81,6 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
private final QSPanelController mQsPanelController;
private final QuickQSPanelController mQuickQSPanelController;
private final QuickStatusBarHeader mQuickStatusBarHeader;
- private final QSFgsManagerFooter mFgsManagerFooter;
- private final QSSecurityFooter mSecurityFooter;
private final QS mQs;
@Nullable
@@ -105,7 +103,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
private TouchAnimator mNonfirstPageAlphaAnimator;
// TranslatesY the QS Tile layout using QS.getHeightDiff()
private TouchAnimator mQSTileLayoutTranslatorAnimator;
- // This animates fading of SecurityFooter and media divider
+ // This animates fading of media player
private TouchAnimator mAllPagesDelayedAnimator;
// Animator for brightness slider(s)
@Nullable
@@ -146,7 +144,6 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
public QSAnimator(QS qs, QuickQSPanel quickPanel, QuickStatusBarHeader quickStatusBarHeader,
QSPanelController qsPanelController,
QuickQSPanelController quickQSPanelController, QSTileHost qsTileHost,
- QSFgsManagerFooter fgsManagerFooter, QSSecurityFooter securityFooter,
@Main Executor executor, TunerService tunerService,
QSExpansionPathInterpolator qsExpansionPathInterpolator) {
mQs = qs;
@@ -154,8 +151,6 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
mQsPanelController = qsPanelController;
mQuickQSPanelController = quickQSPanelController;
mQuickStatusBarHeader = quickStatusBarHeader;
- mFgsManagerFooter = fgsManagerFooter;
- mSecurityFooter = securityFooter;
mHost = qsTileHost;
mExecutor = executor;
mTunerService = tunerService;
@@ -472,10 +467,8 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
.setListener(this)
.build();
- // Fade in the security footer and the divider as we reach the final position
+ // Fade in the media player as we reach the final position
Builder builder = new Builder().setStartDelay(EXPANDED_TILE_DELAY);
- builder.addFloat(mFgsManagerFooter.getView(), "alpha", 0, 1);
- builder.addFloat(mSecurityFooter.getView(), "alpha", 0, 1);
if (mQsPanelController.shouldUseHorizontalLayout()
&& mQsPanelController.mMediaHost.hostView != null) {
builder.addFloat(mQsPanelController.mMediaHost.hostView, "alpha", 0, 1);
@@ -484,8 +477,6 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
mQsPanelController.mMediaHost.hostView.setAlpha(1.0f);
}
mAllPagesDelayedAnimator = builder.build();
- mAllViews.add(mFgsManagerFooter.getView());
- mAllViews.add(mSecurityFooter.getView());
translationYBuilder.setInterpolator(mQSExpansionPathInterpolator.getYInterpolator());
qqsTranslationYBuilder.setInterpolator(mQSExpansionPathInterpolator.getYInterpolator());
translationXBuilder.setInterpolator(mQSExpansionPathInterpolator.getXInterpolator());
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 519ed5ceeab4..66584414e291 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -138,12 +138,8 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {
}
void updateResources(QSPanelController qsPanelController,
- QuickStatusBarHeaderController quickStatusBarHeaderController,
- boolean newFooter) {
- int bottomPadding = 0;
- if (newFooter) {
- bottomPadding = getResources().getDimensionPixelSize(R.dimen.qs_panel_padding_bottom);
- }
+ QuickStatusBarHeaderController quickStatusBarHeaderController) {
+ int bottomPadding = getResources().getDimensionPixelSize(R.dimen.qs_panel_padding_bottom);
mQSPanelContainer.setPaddingRelative(
mQSPanelContainer.getPaddingStart(),
QSUtils.getQsHeaderSystemIconsAreaHeight(mContext),
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
index 61da18224023..7d61991c910a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
@@ -18,8 +18,6 @@ package com.android.systemui.qs;
import android.content.res.Configuration;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.ViewController;
@@ -32,26 +30,23 @@ public class QSContainerImplController extends ViewController<QSContainerImpl> {
private final QSPanelController mQsPanelController;
private final QuickStatusBarHeaderController mQuickStatusBarHeaderController;
private final ConfigurationController mConfigurationController;
- private final boolean mNewFooter;
private final ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@Override
public void onConfigChanged(Configuration newConfig) {
- mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController, mNewFooter);
+ mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController);
}
};
@Inject
QSContainerImplController(QSContainerImpl view, QSPanelController qsPanelController,
QuickStatusBarHeaderController quickStatusBarHeaderController,
- ConfigurationController configurationController,
- FeatureFlags featureFlags) {
+ ConfigurationController configurationController) {
super(view);
mQsPanelController = qsPanelController;
mQuickStatusBarHeaderController = quickStatusBarHeaderController;
mConfigurationController = configurationController;
- mNewFooter = featureFlags.isEnabled(Flags.NEW_FOOTER);
}
@Override
@@ -65,7 +60,7 @@ public class QSContainerImplController extends ViewController<QSContainerImpl> {
@Override
protected void onViewAttached() {
- mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController, mNewFooter);
+ mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController);
mConfigurationController.addCallback(mConfigurationListener);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 795a606fe7e8..4fa05c833f53 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -211,7 +211,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
}
});
mHeader = view.findViewById(R.id.header);
- mQSPanelController.setHeaderContainer(view.findViewById(R.id.header_text_container));
mFooter = qsFragmentComponent.getQSFooter();
mQSContainerImplController = qsFragmentComponent.getQSContainerImplController();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 8c4dedc577d1..9fbdd3c873e2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -75,7 +75,6 @@ public class QSPanel extends LinearLayout implements Tunable {
@Nullable
protected BrightnessSliderController mToggleSliderController;
- private final H mHandler = new H();
/** Whether or not the QS media player feature is enabled. */
protected boolean mUsingMediaPlayer;
@@ -87,16 +86,9 @@ public class QSPanel extends LinearLayout implements Tunable {
new ArrayList<>();
@Nullable
- protected View mFgsManagerFooter;
- @Nullable
- protected View mSecurityFooter;
-
- @Nullable
protected View mFooter;
@Nullable
- private ViewGroup mHeaderContainer;
- @Nullable
private PageIndicator mFooterPageIndicator;
private int mContentMarginStart;
private int mContentMarginEnd;
@@ -112,7 +104,6 @@ public class QSPanel extends LinearLayout implements Tunable {
private float mSquishinessFraction = 1f;
private final ArrayMap<View, Integer> mChildrenLayoutTop = new ArrayMap<>();
private final Rect mClippingRect = new Rect();
- private boolean mUseNewFooter = false;
private ViewGroup mMediaHostView;
private boolean mShouldMoveMediaOnExpansion = true;
@@ -156,10 +147,6 @@ public class QSPanel extends LinearLayout implements Tunable {
}
}
- void setUseNewFooter(boolean useNewFooter) {
- mUseNewFooter = useNewFooter;
- }
-
protected void setHorizontalContentContainerClipping() {
mHorizontalContentContainer.setClipChildren(true);
mHorizontalContentContainer.setClipToPadding(false);
@@ -444,27 +431,6 @@ public class QSPanel extends LinearLayout implements Tunable {
}
}
- /** Switch the security footer between top and bottom of QS depending on orientation. */
- public void switchSecurityFooter(boolean shouldUseSplitNotificationShade) {
- if (mSecurityFooter == null) return;
-
- if (!shouldUseSplitNotificationShade
- && mContext.getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_LANDSCAPE && mHeaderContainer != null) {
- // Adding the security view to the header, that enables us to avoid scrolling
- switchToParent(mSecurityFooter, mHeaderContainer, 0);
- } else {
- // Add after the footer
- int index;
- if (mFgsManagerFooter != null) {
- index = indexOfChild(mFgsManagerFooter);
- } else {
- index = indexOfChild(mFooter);
- }
- switchToParent(mSecurityFooter, this, index + 1);
- }
- }
-
private void switchToParent(View child, ViewGroup parent, int index) {
switchToParent(child, parent, index, getDumpableTag());
}
@@ -609,38 +575,10 @@ public class QSPanel extends LinearLayout implements Tunable {
}
}
- /**
- * Set the header container of quick settings.
- */
- public void setHeaderContainer(@NonNull ViewGroup headerContainer) {
- mHeaderContainer = headerContainer;
- }
-
public boolean isListening() {
return mListening;
}
- /**
- * Set the security footer view and switch it into the right place
- * @param view the view in question
- * @param shouldUseSplitNotificationShade if QS is in split shade mode
- */
- public void setSecurityFooter(View view, boolean shouldUseSplitNotificationShade) {
- mSecurityFooter = view;
- switchSecurityFooter(shouldUseSplitNotificationShade);
- }
-
- /**
- * Set the fgs manager footer view and switch it into the right place
- * @param view the view in question
- */
- public void setFgsManagerFooter(View view) {
- mFgsManagerFooter = view;
- // Add after the footer
- int index = indexOfChild(mFooter);
- switchToParent(mFgsManagerFooter, this, index + 1);
- }
-
protected void setPageMargin(int pageMargin) {
if (mTileLayout instanceof PagedTileLayout) {
((PagedTileLayout) mTileLayout).setPageMargin(pageMargin);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index b46f9c374253..5670836566ab 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -21,18 +21,14 @@ import static com.android.systemui.media.dagger.MediaModule.QS_PANEL;
import static com.android.systemui.qs.QSPanel.QS_SHOW_BRIGHTNESS;
import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER;
-import android.annotation.NonNull;
import android.content.res.Configuration;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewGroup;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.media.MediaHost;
import com.android.systemui.media.MediaHostState;
@@ -56,8 +52,6 @@ import javax.inject.Named;
@QSScope
public class QSPanelController extends QSPanelControllerBase<QSPanel> {
- private final QSFgsManagerFooter mQSFgsManagerFooter;
- private final QSSecurityFooter mQsSecurityFooter;
private final TunerService mTunerService;
private final QSCustomizerController mQsCustomizerController;
private final QSTileRevealController.Factory mQsTileRevealControllerFactory;
@@ -65,7 +59,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
private final BrightnessController mBrightnessController;
private final BrightnessSliderController mBrightnessSliderController;
private final BrightnessMirrorHandler mBrightnessMirrorHandler;
- private final FeatureFlags mFeatureFlags;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private boolean mGridContentVisible = true;
@@ -75,11 +68,9 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
@Override
public void onConfigurationChange(Configuration newConfig) {
mView.updateResources();
- mQsSecurityFooter.onConfigurationChanged();
if (mView.isListening()) {
refreshAllTiles();
}
- mView.switchSecurityFooter(mShouldUseSplitNotificationShade);
}
};
@@ -94,8 +85,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
};
@Inject
- QSPanelController(QSPanel view, QSFgsManagerFooter qsFgsManagerFooter,
- QSSecurityFooter qsSecurityFooter, TunerService tunerService,
+ QSPanelController(QSPanel view, TunerService tunerService,
QSTileHost qstileHost, QSCustomizerController qsCustomizerController,
@Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
@Named(QS_PANEL) MediaHost mediaHost,
@@ -103,12 +93,10 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory,
BrightnessSliderController.Factory brightnessSliderFactory,
- FalsingManager falsingManager, FeatureFlags featureFlags,
+ FalsingManager falsingManager,
StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
metricsLogger, uiEventLogger, qsLogger, dumpManager);
- mQSFgsManagerFooter = qsFgsManagerFooter;
- mQsSecurityFooter = qsSecurityFooter;
mTunerService = tunerService;
mQsCustomizerController = qsCustomizerController;
mQsTileRevealControllerFactory = qsTileRevealControllerFactory;
@@ -119,9 +107,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
mBrightnessController = brightnessControllerFactory.create(mBrightnessSliderController);
mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController);
- mFeatureFlags = featureFlags;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
- view.setUseNewFooter(featureFlags.isEnabled(Flags.NEW_FOOTER));
}
@Override
@@ -132,7 +118,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
mMediaHost.init(MediaHierarchyManager.LOCATION_QS);
mQsCustomizerController.init();
mBrightnessSliderController.init();
- mQSFgsManagerFooter.init();
}
@Override
@@ -147,10 +132,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
refreshAllTiles();
}
mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener);
- if (!mFeatureFlags.isEnabled(Flags.NEW_FOOTER)) {
- mView.setSecurityFooter(mQsSecurityFooter.getView(), mShouldUseSplitNotificationShade);
- mView.setFgsManagerFooter(mQSFgsManagerFooter.getView());
- }
switchTileLayout(true);
mBrightnessMirrorHandler.onQsPanelAttached();
@@ -172,13 +153,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
super.onViewDetached();
}
- /**
- * Set the header container of quick settings.
- */
- public void setHeaderContainer(@NonNull ViewGroup headerContainer) {
- mView.setHeaderContainer(headerContainer);
- }
-
/** */
public void setVisibility(int visibility) {
mView.setVisibility(visibility);
@@ -191,11 +165,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
refreshAllTiles();
}
- if (!mFeatureFlags.isEnabled(Flags.NEW_FOOTER)) {
- mQSFgsManagerFooter.setListening(listening);
- mQsSecurityFooter.setListening(listening);
- }
-
// Set the listening as soon as the QS fragment starts listening regardless of the
//expansion, so it will update the current brightness before the slider is visible.
if (listening) {
@@ -224,8 +193,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
public void refreshAllTiles() {
mBrightnessController.checkRestrictionAndSetEnabled();
super.refreshAllTiles();
- mQSFgsManagerFooter.refreshState();
- mQsSecurityFooter.refreshState();
}
/** Start customizing the Quick Settings. */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index c5ca285aa312..264edb1ec9e4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -70,7 +70,6 @@ public class QuickStatusBarHeader extends FrameLayout {
private View mDateView;
// DateView next to clock. Visible on QQS
private VariableDateView mClockDateView;
- private View mSecurityHeaderView;
private View mStatusIconsView;
private View mContainer;
@@ -137,7 +136,6 @@ public class QuickStatusBarHeader extends FrameLayout {
mPrivacyChip = findViewById(R.id.privacy_chip);
mDateView = findViewById(R.id.date);
mClockDateView = findViewById(R.id.date_clock);
- mSecurityHeaderView = findViewById(R.id.header_text_container);
mClockIconsSeparator = findViewById(R.id.separator);
mRightLayout = findViewById(R.id.rightLayout);
mDateContainer = findViewById(R.id.date_container);
@@ -152,8 +150,6 @@ public class QuickStatusBarHeader extends FrameLayout {
updateResources();
Configuration config = mContext.getResources().getConfiguration();
setDatePrivacyContainersWidth(config.orientation == Configuration.ORIENTATION_LANDSCAPE);
- setSecurityHeaderContainerVisibility(
- config.orientation == Configuration.ORIENTATION_LANDSCAPE);
// QS will always show the estimate, and BatteryMeterView handles the case where
// it's unavailable or charging
@@ -207,8 +203,6 @@ public class QuickStatusBarHeader extends FrameLayout {
super.onConfigurationChanged(newConfig);
updateResources();
setDatePrivacyContainersWidth(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE);
- setSecurityHeaderContainerVisibility(
- newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE);
}
@Override
@@ -229,10 +223,6 @@ public class QuickStatusBarHeader extends FrameLayout {
mPrivacyContainer.setLayoutParams(lp);
}
- private void setSecurityHeaderContainerVisibility(boolean landscape) {
- mSecurityHeaderView.setVisibility(landscape ? VISIBLE : GONE);
- }
-
private void updateBatteryMode() {
if (mConfigShowBatteryEstimate && !mHasCenterCutout) {
mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE);
@@ -337,7 +327,6 @@ public class QuickStatusBarHeader extends FrameLayout {
return;
}
TouchAnimator.Builder builder = new TouchAnimator.Builder()
- .addFloat(mSecurityHeaderView, "alpha", 0, 1)
// These views appear on expanding down
.addFloat(mDateView, "alpha", 0, 0, 1)
.addFloat(mClockDateView, "alpha", 1, 0, 0)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
index 2780b163e5ca..aa505fb0b6bd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
@@ -22,13 +22,10 @@ import static com.android.systemui.util.Utils.useQsMediaPlayer;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewStub;
import com.android.systemui.R;
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.dagger.qualifiers.RootView;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.privacy.OngoingPrivacyChip;
import com.android.systemui.qs.FooterActionsView;
@@ -128,15 +125,7 @@ public interface QSFragmentModule {
* This will replace a ViewStub either in {@link QSFooterView} or in {@link QSContainerImpl}.
*/
@Provides
- static FooterActionsView providesQSFooterActionsView(@RootView View view,
- FeatureFlags featureFlags) {
- ViewStub stub;
- if (featureFlags.isEnabled(Flags.NEW_FOOTER)) {
- stub = view.requireViewById(R.id.container_stub);
- } else {
- stub = view.requireViewById(R.id.footer_stub);
- }
- stub.inflate();
+ static FooterActionsView providesQSFooterActionsView(@RootView View view) {
return view.findViewById(R.id.qs_footer_actions);
}
@@ -161,9 +150,10 @@ public interface QSFragmentModule {
@Named(QS_SECURITY_FOOTER_VIEW)
static View providesQSSecurityFooterView(
@QSThemedContext LayoutInflater layoutInflater,
- QSPanel qsPanel
+ FooterActionsView footerActionsView
) {
- return layoutInflater.inflate(R.layout.quick_settings_security_footer, qsPanel, false);
+ return layoutInflater.inflate(R.layout.quick_settings_security_footer, footerActionsView,
+ false);
}
/** */
@@ -200,8 +190,8 @@ public interface QSFragmentModule {
@Named(QS_FGS_MANAGER_FOOTER_VIEW)
static View providesQSFgsManagerFooterView(
@QSThemedContext LayoutInflater layoutInflater,
- QSPanel qsPanel
+ FooterActionsView footerActionsView
) {
- return layoutInflater.inflate(R.layout.fgs_footer, qsPanel, false);
+ return layoutInflater.inflate(R.layout.fgs_footer, footerActionsView, false);
}
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
index 7d8a28fc011e..1004fcae3827 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
@@ -116,6 +116,11 @@ public class BatterySaverTile extends QSTileImpl<BooleanState> implements
public void handleSetListening(boolean listening) {
super.handleSetListening(listening);
mSetting.setListening(listening);
+ if (!listening) {
+ // If we stopped listening, it means that the tile is not visible. In that case, we
+ // don't need to save the view anymore
+ mBatteryController.clearLastPowerSaverStartView();
+ }
}
@Override
@@ -128,7 +133,7 @@ public class BatterySaverTile extends QSTileImpl<BooleanState> implements
if (getState().state == Tile.STATE_UNAVAILABLE) {
return;
}
- mBatteryController.setPowerSaveMode(!mPowerSave);
+ mBatteryController.setPowerSaveMode(!mPowerSave, view);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 5d6bbae1f14a..4afd39e36ce3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -202,7 +202,7 @@ public class CastTile extends QSTileImpl<BooleanState> {
mActivityStarter
.postStartActivityDismissingKeyguard(getLongClickIntent(), 0,
controller);
- }, R.style.Theme_SystemUI_Dialog, false /* showProgressBarWhenEmpty */);
+ }, R.style.Theme_SystemUI_Dialog_Cast, false /* showProgressBarWhenEmpty */);
holder.init(dialog);
SystemUIDialog.setShowForAllUsers(dialog, true);
SystemUIDialog.registerDismissListener(dialog);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 41e468d382a3..50ee1f7ba97a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -246,7 +246,9 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
new ClipData.Item(uri));
sharingIntent.setClipData(clipdata);
sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
- sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ .addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
// Make sure pending intents for the system user are still unique across users
// by setting the (otherwise unused) request code to the current user id.
@@ -256,6 +258,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
// cancel current pending intent (if any) since clipData isn't used for matching
PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
context, 0, sharingChooserIntent,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 7239d0cc361b..1df40915a52c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -102,12 +102,14 @@ public class NotificationMediaManager implements Dumpable {
KeyguardStateController.class);
private final KeyguardBypassController mKeyguardBypassController;
private static final HashSet<Integer> PAUSED_MEDIA_STATES = new HashSet<>();
+ private static final HashSet<Integer> CONNECTING_MEDIA_STATES = new HashSet<>();
static {
PAUSED_MEDIA_STATES.add(PlaybackState.STATE_NONE);
PAUSED_MEDIA_STATES.add(PlaybackState.STATE_STOPPED);
PAUSED_MEDIA_STATES.add(PlaybackState.STATE_PAUSED);
PAUSED_MEDIA_STATES.add(PlaybackState.STATE_ERROR);
- PAUSED_MEDIA_STATES.add(PlaybackState.STATE_CONNECTING);
+ CONNECTING_MEDIA_STATES.add(PlaybackState.STATE_CONNECTING);
+ CONNECTING_MEDIA_STATES.add(PlaybackState.STATE_BUFFERING);
}
private final NotificationVisibilityProvider mVisibilityProvider;
@@ -363,7 +365,17 @@ public class NotificationMediaManager implements Dumpable {
* @return true if playing
*/
public static boolean isPlayingState(int state) {
- return !PAUSED_MEDIA_STATES.contains(state);
+ return !PAUSED_MEDIA_STATES.contains(state)
+ && !CONNECTING_MEDIA_STATES.contains(state);
+ }
+
+ /**
+ * Check if a state should be considered as connecting
+ * @param state a PlaybackState
+ * @return true if connecting or buffering
+ */
+ public static boolean isConnectingState(int state) {
+ return CONNECTING_MEDIA_STATES.contains(state);
}
public void setUpWithPresenter(NotificationPresenter presenter) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index eaa66bbc15ac..633786ffa787 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -594,16 +594,13 @@ public class NotificationShelf extends ActivatableNotificationView implements
} else {
shouldClipOwnTop = view.showingPulsing();
}
- if (viewEnd > notificationClipEnd && !shouldClipOwnTop
- && (mAmbientState.isShadeExpanded() || !isPinned)) {
- int clipBottomAmount = (int) (viewEnd - notificationClipEnd);
- if (isPinned) {
- clipBottomAmount = Math.min(view.getIntrinsicHeight() - view.getCollapsedHeight(),
- clipBottomAmount);
+ if (!isPinned) {
+ if (viewEnd > notificationClipEnd && !shouldClipOwnTop) {
+ int clipBottomAmount = (int) (viewEnd - notificationClipEnd);
+ view.setClipBottomAmount(clipBottomAmount);
+ } else {
+ view.setClipBottomAmount(0);
}
- view.setClipBottomAmount(clipBottomAmount);
- } else {
- view.setClipBottomAmount(0);
}
if (shouldClipOwnTop) {
return (int) (viewEnd - getTranslationY());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
index 4a6d7e184ec2..8d7fc98164c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
@@ -21,8 +21,6 @@ import android.widget.TextView;
import com.android.settingslib.WirelessUtils;
-import java.util.List;
-
/** Shows the operator name */
public class OperatorNameView extends TextView {
private boolean mDemoMode;
@@ -43,8 +41,10 @@ public class OperatorNameView extends TextView {
mDemoMode = demoMode;
}
- void update(boolean showOperatorName, boolean hasMobile,
- List<OperatorNameViewController.SubInfo> subs) {
+ void update(boolean showOperatorName,
+ boolean hasMobile,
+ OperatorNameViewController.SubInfo sub
+ ) {
setVisibility(showOperatorName ? VISIBLE : GONE);
boolean airplaneMode = WirelessUtils.isAirplaneModeOn(mContext);
@@ -55,24 +55,21 @@ public class OperatorNameView extends TextView {
}
if (!mDemoMode) {
- updateText(subs);
+ updateText(sub);
}
}
- void updateText(List<OperatorNameViewController.SubInfo> subs) {
+ void updateText(OperatorNameViewController.SubInfo subInfo) {
+ CharSequence carrierName = null;
CharSequence displayText = null;
- final int N = subs.size();
- for (int i = 0; i < N; i++) {
- OperatorNameViewController.SubInfo subInfo = subs.get(i);
- CharSequence carrierName = subs.get(i).getCarrierName();
- if (!TextUtils.isEmpty(carrierName) && subInfo.simReady()) {
- if (subInfo.stateInService()) {
- displayText = subInfo.getCarrierName();
- break;
- }
+ if (subInfo != null) {
+ carrierName = subInfo.getCarrierName();
+ }
+ if (!TextUtils.isEmpty(carrierName) && subInfo.simReady()) {
+ if (subInfo.stateInService()) {
+ displayText = carrierName;
}
}
-
setText(displayText);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java
index 8a4c4b5ac5c6..8afc72f08656 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameViewController.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.os.Bundle;
import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.view.View;
@@ -30,12 +31,11 @@ import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.statusbar.connectivity.IconState;
import com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.connectivity.SignalCallback;
+import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
import com.android.systemui.tuner.TunerService;
+import com.android.systemui.util.CarrierConfigTracker;
import com.android.systemui.util.ViewController;
-import java.util.ArrayList;
-import java.util.List;
-
import javax.inject.Inject;
/** Controller for {@link OperatorNameView}. */
@@ -47,19 +47,22 @@ public class OperatorNameViewController extends ViewController<OperatorNameView>
private final TunerService mTunerService;
private final TelephonyManager mTelephonyManager;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final CarrierConfigTracker mCarrierConfigTracker;
private OperatorNameViewController(OperatorNameView view,
DarkIconDispatcher darkIconDispatcher,
NetworkController networkController,
TunerService tunerService,
TelephonyManager telephonyManager,
- KeyguardUpdateMonitor keyguardUpdateMonitor) {
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ CarrierConfigTracker carrierConfigTracker) {
super(view);
mDarkIconDispatcher = darkIconDispatcher;
mNetworkController = networkController;
mTunerService = tunerService;
mTelephonyManager = telephonyManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mCarrierConfigTracker = carrierConfigTracker;
}
@Override
@@ -79,24 +82,22 @@ public class OperatorNameViewController extends ViewController<OperatorNameView>
}
private void update() {
- mView.update(mTunerService.getValue(KEY_SHOW_OPERATOR_NAME, 1) != 0,
- mTelephonyManager.isDataCapable(), getSubInfos());
+ SubInfo defaultSubInfo = getDefaultSubInfo();
+ boolean showOperatorName =
+ mCarrierConfigTracker
+ .getShowOperatorNameInStatusBarConfig(defaultSubInfo.getSubId())
+ && (mTunerService.getValue(KEY_SHOW_OPERATOR_NAME, 1) != 0);
+ mView.update(showOperatorName, mTelephonyManager.isDataCapable(), getDefaultSubInfo());
}
- private List<SubInfo> getSubInfos() {
- List<SubInfo> result = new ArrayList<>();
- List<SubscriptionInfo> subscritionInfos =
- mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false);
-
- for (SubscriptionInfo subscriptionInfo : subscritionInfos) {
- int subId = subscriptionInfo.getSubscriptionId();
- result.add(new SubInfo(
- subscriptionInfo.getCarrierName(),
- mKeyguardUpdateMonitor.getSimState(subId),
- mKeyguardUpdateMonitor.getServiceState(subId)));
- }
-
- return result;
+ private SubInfo getDefaultSubInfo() {
+ int defaultSubId = SubscriptionManager.getDefaultDataSubscriptionId();
+ SubscriptionInfo sI = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(defaultSubId);
+ return new SubInfo(
+ sI.getSubscriptionId(),
+ sI.getCarrierName(),
+ mKeyguardUpdateMonitor.getSimState(defaultSubId),
+ mKeyguardUpdateMonitor.getServiceState(defaultSubId));
}
/** Factory for constructing an {@link OperatorNameViewController}. */
@@ -106,22 +107,32 @@ public class OperatorNameViewController extends ViewController<OperatorNameView>
private final TunerService mTunerService;
private final TelephonyManager mTelephonyManager;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final CarrierConfigTracker mCarrierConfigTracker;
@Inject
- public Factory(DarkIconDispatcher darkIconDispatcher, NetworkController networkController,
- TunerService tunerService, TelephonyManager telephonyManager,
- KeyguardUpdateMonitor keyguardUpdateMonitor) {
+ public Factory(DarkIconDispatcher darkIconDispatcher,
+ NetworkController networkController,
+ TunerService tunerService,
+ TelephonyManager telephonyManager,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ CarrierConfigTracker carrierConfigTracker) {
mDarkIconDispatcher = darkIconDispatcher;
mNetworkController = networkController;
mTunerService = tunerService;
mTelephonyManager = telephonyManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mCarrierConfigTracker = carrierConfigTracker;
}
/** Create an {@link OperatorNameViewController}. */
public OperatorNameViewController create(OperatorNameView view) {
- return new OperatorNameViewController(view, mDarkIconDispatcher, mNetworkController,
- mTunerService, mTelephonyManager, mKeyguardUpdateMonitor);
+ return new OperatorNameViewController(view,
+ mDarkIconDispatcher,
+ mNetworkController,
+ mTunerService,
+ mTelephonyManager,
+ mKeyguardUpdateMonitor,
+ mCarrierConfigTracker);
}
}
@@ -152,7 +163,7 @@ public class OperatorNameViewController extends ViewController<OperatorNameView>
new KeyguardUpdateMonitorCallback() {
@Override
public void onRefreshCarrierInfo() {
- mView.updateText(getSubInfos());
+ mView.updateText(getDefaultSubInfo());
}
};
@@ -176,17 +187,26 @@ public class OperatorNameViewController extends ViewController<OperatorNameView>
};
static class SubInfo {
+ private final int mSubId;
private final CharSequence mCarrierName;
private final int mSimState;
private final ServiceState mServiceState;
- private SubInfo(CharSequence carrierName,
- int simState, ServiceState serviceState) {
+ private SubInfo(
+ int subId,
+ CharSequence carrierName,
+ int simState,
+ ServiceState serviceState) {
+ mSubId = subId;
mCarrierName = carrierName;
mSimState = simState;
mServiceState = serviceState;
}
+ int getSubId() {
+ return mSubId;
+ }
+
boolean simReady() {
return mSimState == TelephonyManager.SIM_STATE_READY;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
index b95d153e8bd1..7f3381ccd38a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
@@ -34,10 +34,13 @@ public class ExpandableViewState extends ViewState {
private static final int TAG_ANIMATOR_HEIGHT = R.id.height_animator_tag;
private static final int TAG_ANIMATOR_TOP_INSET = R.id.top_inset_animator_tag;
+ private static final int TAG_ANIMATOR_BOTTOM_INSET = R.id.bottom_inset_animator_tag;
private static final int TAG_END_HEIGHT = R.id.height_animator_end_value_tag;
private static final int TAG_END_TOP_INSET = R.id.top_inset_animator_end_value_tag;
+ private static final int TAG_END_BOTTOM_INSET = R.id.bottom_inset_animator_end_value_tag;
private static final int TAG_START_HEIGHT = R.id.height_animator_start_value_tag;
private static final int TAG_START_TOP_INSET = R.id.top_inset_animator_start_value_tag;
+ private static final int TAG_START_BOTTOM_INSET = R.id.bottom_inset_animator_start_value_tag;
// These are flags such that we can create masks for filtering.
@@ -96,12 +99,17 @@ public class ExpandableViewState extends ViewState {
public boolean headsUpIsVisible;
/**
- * How much the child overlaps with the previous child on top. This is used to
- * show the background properly when the child on top is translating away.
+ * How much the child overlaps on top with the child above.
*/
public int clipTopAmount;
/**
+ * How much the child overlaps on bottom with the child above. This is used to
+ * show the background properly when the child on top is translating away.
+ */
+ public int clipBottomAmount;
+
+ /**
* The index of the view, only accounting for views not equal to GONE
*/
public int notGoneIndex;
@@ -138,8 +146,8 @@ public class ExpandableViewState extends ViewState {
if (view instanceof ExpandableView) {
ExpandableView expandableView = (ExpandableView) view;
- int height = expandableView.getActualHeight();
- int newHeight = this.height;
+ final int height = expandableView.getActualHeight();
+ final int newHeight = this.height;
// apply height
if (height != newHeight) {
@@ -157,10 +165,14 @@ public class ExpandableViewState extends ViewState {
expandableView.setBelowSpeedBump(this.belowSpeedBump);
// apply clipping
- float oldClipTopAmount = expandableView.getClipTopAmount();
+ final float oldClipTopAmount = expandableView.getClipTopAmount();
if (oldClipTopAmount != this.clipTopAmount) {
expandableView.setClipTopAmount(this.clipTopAmount);
}
+ final float oldClipBottomAmount = expandableView.getClipBottomAmount();
+ if (oldClipBottomAmount != this.clipBottomAmount) {
+ expandableView.setClipBottomAmount(this.clipBottomAmount);
+ }
expandableView.setTransformingInShelf(false);
expandableView.setInShelf(inShelf);
@@ -187,13 +199,20 @@ public class ExpandableViewState extends ViewState {
abortAnimation(child, TAG_ANIMATOR_HEIGHT);
}
- // start top inset animation
+ // start clip top animation
if (this.clipTopAmount != expandableView.getClipTopAmount()) {
- startInsetAnimation(expandableView, properties);
+ startClipAnimation(expandableView, properties, /* clipTop */true);
} else {
abortAnimation(child, TAG_ANIMATOR_TOP_INSET);
}
+ // start clip bottom animation
+ if (this.clipBottomAmount != expandableView.getClipBottomAmount()) {
+ startClipAnimation(expandableView, properties, /* clipTop */ false);
+ } else {
+ abortAnimation(child, TAG_ANIMATOR_BOTTOM_INSET);
+ }
+
// start dimmed animation
expandableView.setDimmed(this.dimmed, animationFilter.animateDimmed);
@@ -301,16 +320,20 @@ public class ExpandableViewState extends ViewState {
child.setActualHeightAnimating(true);
}
- private void startInsetAnimation(final ExpandableView child, AnimationProperties properties) {
- Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET);
- Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET);
- int newEndValue = this.clipTopAmount;
+ private void startClipAnimation(final ExpandableView child, AnimationProperties properties,
+ boolean clipTop) {
+ Integer previousStartValue = getChildTag(child,
+ clipTop ? TAG_START_TOP_INSET : TAG_START_BOTTOM_INSET);
+ Integer previousEndValue = getChildTag(child,
+ clipTop ? TAG_END_TOP_INSET : TAG_END_BOTTOM_INSET);
+ int newEndValue = clipTop ? this.clipTopAmount : this.clipBottomAmount;
if (previousEndValue != null && previousEndValue == newEndValue) {
return;
}
- ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TOP_INSET);
+ ValueAnimator previousAnimator = getChildTag(child,
+ clipTop ? TAG_ANIMATOR_TOP_INSET : TAG_ANIMATOR_BOTTOM_INSET);
AnimationFilter filter = properties.getAnimationFilter();
- if (!filter.animateTopInset) {
+ if (clipTop && !filter.animateTopInset || !clipTop) {
// just a local update was performed
if (previousAnimator != null) {
// we need to increase all animation keyframes of the previous animator by the
@@ -319,22 +342,28 @@ public class ExpandableViewState extends ViewState {
int relativeDiff = newEndValue - previousEndValue;
int newStartValue = previousStartValue + relativeDiff;
values[0].setIntValues(newStartValue, newEndValue);
- child.setTag(TAG_START_TOP_INSET, newStartValue);
- child.setTag(TAG_END_TOP_INSET, newEndValue);
+ child.setTag(clipTop ? TAG_START_TOP_INSET : TAG_START_BOTTOM_INSET, newStartValue);
+ child.setTag(clipTop ? TAG_END_TOP_INSET : TAG_END_BOTTOM_INSET, newEndValue);
previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
return;
} else {
// no new animation needed, let's just apply the value
- child.setClipTopAmount(newEndValue);
+ if (clipTop) {
+ child.setClipTopAmount(newEndValue);
+ } else {
+ child.setClipBottomAmount(newEndValue);
+ }
return;
}
}
- ValueAnimator animator = ValueAnimator.ofInt(child.getClipTopAmount(), newEndValue);
- animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
+ ValueAnimator animator = ValueAnimator.ofInt(
+ clipTop ? child.getClipTopAmount() : child.getClipBottomAmount(), newEndValue);
+ animator.addUpdateListener(animation -> {
+ if (clipTop) {
child.setClipTopAmount((int) animation.getAnimatedValue());
+ } else {
+ child.setClipBottomAmount((int) animation.getAnimatedValue());
}
});
animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
@@ -353,15 +382,16 @@ public class ExpandableViewState extends ViewState {
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- child.setTag(TAG_ANIMATOR_TOP_INSET, null);
- child.setTag(TAG_START_TOP_INSET, null);
- child.setTag(TAG_END_TOP_INSET, null);
+ child.setTag(clipTop ? TAG_ANIMATOR_TOP_INSET : TAG_ANIMATOR_BOTTOM_INSET, null);
+ child.setTag(clipTop ? TAG_START_TOP_INSET : TAG_START_BOTTOM_INSET, null);
+ child.setTag(clipTop ? TAG_END_TOP_INSET : TAG_END_BOTTOM_INSET, null);
}
});
startAnimator(animator, listener);
- child.setTag(TAG_ANIMATOR_TOP_INSET, animator);
- child.setTag(TAG_START_TOP_INSET, child.getClipTopAmount());
- child.setTag(TAG_END_TOP_INSET, newEndValue);
+ child.setTag(clipTop ? TAG_ANIMATOR_TOP_INSET:TAG_ANIMATOR_BOTTOM_INSET, animator);
+ child.setTag(clipTop ? TAG_START_TOP_INSET: TAG_START_BOTTOM_INSET,
+ clipTop ? child.getClipTopAmount() : child.getClipBottomAmount());
+ child.setTag(clipTop ? TAG_END_TOP_INSET: TAG_END_BOTTOM_INSET, newEndValue);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index 6c6ed850f45a..d68f37103510 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -139,6 +139,8 @@ constructor(
height += spaceNeeded
count += 1
} else {
+ val gapBeforeFirstViewInShelf = current.calculateGapHeight(stack, previous, count)
+ height += gapBeforeFirstViewInShelf
height += shelfHeight
log { "returning height with shelf -> $height" }
return height
@@ -178,7 +180,9 @@ constructor(
if (visibleIndex != 0) {
size += notificationPadding
}
- size += calculateGapHeight(stack, previousView, visibleIndex)
+ val gapHeight = calculateGapHeight(stack, previousView, visibleIndex)
+ log { "\ti=$visibleIndex gapHeight=$gapHeight"}
+ size += gapHeight
return size
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index e1f8c35c3755..c0971337a19f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -192,6 +192,7 @@ public class StackScrollAlgorithm {
float clipStart = 0;
int childCount = algorithmState.visibleChildren.size();
boolean firstHeadsUp = true;
+ float firstHeadsUpEnd = 0;
for (int i = 0; i < childCount; i++) {
ExpandableView child = algorithmState.visibleChildren.get(i);
ExpandableViewState state = child.getViewState();
@@ -203,14 +204,18 @@ public class StackScrollAlgorithm {
float newNotificationEnd = newYTranslation + newHeight;
boolean isHeadsUp = (child instanceof ExpandableNotificationRow) && child.isPinned();
if (mClipNotificationScrollToTop
- && (!state.inShelf || (isHeadsUp && !firstHeadsUp))
- && newYTranslation < clipStart
+ && ((isHeadsUp && !firstHeadsUp) || child.isHeadsUpAnimatingAway())
+ && newNotificationEnd > firstHeadsUpEnd
&& !ambientState.isShadeExpanded()) {
- // The previous view is overlapping on top, clip!
- float overlapAmount = clipStart - newYTranslation;
- state.clipTopAmount = (int) overlapAmount;
+ // The bottom of this view is peeking out from under the previous view.
+ // Clip the part that is peeking out.
+ float overlapAmount = newNotificationEnd - firstHeadsUpEnd;
+ state.clipBottomAmount = (int) overlapAmount;
} else {
- state.clipTopAmount = 0;
+ state.clipBottomAmount = 0;
+ }
+ if (firstHeadsUp) {
+ firstHeadsUpEnd = newNotificationEnd;
}
if (isHeadsUp) {
firstHeadsUp = false;
@@ -635,8 +640,6 @@ public class StackScrollAlgorithm {
// Ensure that a headsUp doesn't vertically extend further than the heads-up at
// the top most z-position
childState.height = row.getIntrinsicHeight();
- childState.yTranslation = Math.min(topState.yTranslation + topState.height
- - childState.height, childState.yTranslation);
}
// heads up notification show and this row is the top entry of heads up
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt
index 745228e72596..491e9fde5c9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt
@@ -168,14 +168,7 @@ class NotificationsQSContainerController @Inject constructor(
private fun updateBottomSpacing() {
val (containerPadding, notificationsMargin) = calculateBottomSpacing()
var qsScrollPaddingBottom = 0
- val newFooter = featureFlags.isEnabled(Flags.NEW_FOOTER)
- if (!newFooter && !(splitShadeEnabled || isQSCustomizing || isQSDetailShowing ||
- isGestureNavigation || taskbarVisible)) {
- // no taskbar, portrait, navigation buttons enabled:
- // padding is needed so QS can scroll up over bottom insets - to reach the point when
- // the whole QS is above bottom insets
- qsScrollPaddingBottom = bottomStableInsets
- } else if (newFooter && !(isQSCustomizing || isQSDetailShowing)) {
+ if (!(isQSCustomizing || isQSDetailShowing)) {
// With the new footer, we also want this padding in the bottom in these cases
qsScrollPaddingBottom = if (splitShadeEnabled) {
notificationsMargin - scrimShadeBottomMargin
@@ -185,11 +178,7 @@ class NotificationsQSContainerController @Inject constructor(
}
mView.setPadding(0, 0, 0, containerPadding)
mView.setNotificationsMarginBottom(notificationsMargin)
- if (newFooter) {
- mView.setQSContainerPaddingBottom(qsScrollPaddingBottom)
- } else {
- mView.setQSScrollPaddingBottom(qsScrollPaddingBottom)
- }
+ mView.setQSContainerPaddingBottom(qsScrollPaddingBottom)
}
private fun calculateBottomSpacing(): Pair<Int, Int> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
index 7caea06e6359..2446cf7ba412 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
@@ -103,16 +103,6 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout
mStackScroller.setLayoutParams(params);
}
- public void setQSScrollPaddingBottom(int paddingBottom) {
- if (mQSScrollView != null) {
- mQSScrollView.setPaddingRelative(
- mQSScrollView.getPaddingLeft(),
- mQSScrollView.getPaddingTop(),
- mQSScrollView.getPaddingRight(),
- paddingBottom);
- }
- }
-
public void setQSContainerPaddingBottom(int paddingBottom) {
if (mQSContainer != null) {
mQSContainer.setPadding(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 6fe92fafc075..87ca942edff2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -489,7 +489,7 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
ExpandableNotificationRow row,
boolean animate,
boolean isActivityIntent) {
- mLogger.logStartNotificationIntent(entry.getKey(), intent);
+ mLogger.logStartNotificationIntent(entry.getKey());
try {
Runnable onFinishAnimationCallback = animate
? () -> mLaunchEventsEmitter.notifyFinishLaunchNotifActivity(entry)
@@ -513,8 +513,10 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
mKeyguardStateController.isShowing(),
eventTime)
: getActivityOptions(mCentralSurfaces.getDisplayId(), adapter);
- return intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
+ int result = intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
null, null, options);
+ mLogger.logSendPendingIntent(entry.getKey(), intent, result);
+ return result;
});
} catch (PendingIntent.CanceledException e) {
// the stack trace isn't very helpful here.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
index d118747a0365..2fbe520a4b61 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
@@ -32,7 +32,7 @@ class StatusBarNotificationActivityStarterLogger @Inject constructor(
buffer.log(TAG, DEBUG, {
str1 = key
}, {
- "(1/4) onNotificationClicked: $str1"
+ "(1/5) onNotificationClicked: $str1"
})
}
@@ -40,7 +40,7 @@ class StatusBarNotificationActivityStarterLogger @Inject constructor(
buffer.log(TAG, DEBUG, {
str1 = key
}, {
- "(2/4) handleNotificationClickAfterKeyguardDismissed: $str1"
+ "(2/5) handleNotificationClickAfterKeyguardDismissed: $str1"
})
}
@@ -48,16 +48,25 @@ class StatusBarNotificationActivityStarterLogger @Inject constructor(
buffer.log(TAG, DEBUG, {
str1 = key
}, {
- "(3/4) handleNotificationClickAfterPanelCollapsed: $str1"
+ "(3/5) handleNotificationClickAfterPanelCollapsed: $str1"
})
}
- fun logStartNotificationIntent(key: String, pendingIntent: PendingIntent) {
+ fun logStartNotificationIntent(key: String) {
+ buffer.log(TAG, INFO, {
+ str1 = key
+ }, {
+ "(4/5) startNotificationIntent: $str1"
+ })
+ }
+
+ fun logSendPendingIntent(key: String, pendingIntent: PendingIntent, result: Int) {
buffer.log(TAG, INFO, {
str1 = key
str2 = pendingIntent.intent.toString()
+ int1 = result
}, {
- "(4/4) Starting $str2 for notification $str1"
+ "(5/5) Started intent $str2 for notification $str1 with result code $int1"
})
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 6c6ec192646d..06532c4f9d17 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -61,6 +61,7 @@ import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.tuner.TunerService;
+import com.android.systemui.util.CarrierConfigTracker;
import com.android.systemui.util.settings.SecureSettings;
import java.util.concurrent.Executor;
@@ -263,6 +264,7 @@ public abstract class StatusBarViewModule {
NetworkController networkController,
StatusBarStateController statusBarStateController,
CommandQueue commandQueue,
+ CarrierConfigTracker carrierConfigTracker,
CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger,
OperatorNameViewController.Factory operatorNameViewControllerFactory,
SecureSettings secureSettings,
@@ -282,6 +284,7 @@ public abstract class StatusBarViewModule {
networkController,
statusBarStateController,
commandQueue,
+ carrierConfigTracker,
collapsedStatusBarFragmentLogger,
operatorNameViewControllerFactory,
secureSettings,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 8194957c52fc..9e48b763f4f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -32,6 +32,7 @@ import android.database.ContentObserver;
import android.os.Bundle;
import android.os.Parcelable;
import android.provider.Settings;
+import android.telephony.SubscriptionManager;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
@@ -69,6 +70,9 @@ import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener;
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.policy.EncryptionHelper;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.CarrierConfigTracker;
+import com.android.systemui.util.CarrierConfigTracker.CarrierConfigChangedListener;
+import com.android.systemui.util.CarrierConfigTracker.DefaultDataSubscriptionChangedListener;
import com.android.systemui.util.settings.SecureSettings;
import java.util.ArrayList;
@@ -115,6 +119,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
private final NotificationIconAreaController mNotificationIconAreaController;
private final PanelExpansionStateManager mPanelExpansionStateManager;
private final StatusBarIconController mStatusBarIconController;
+ private final CarrierConfigTracker mCarrierConfigTracker;
private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
private final SecureSettings mSecureSettings;
private final Executor mMainExecutor;
@@ -137,6 +142,28 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
private OperatorNameViewController mOperatorNameViewController;
private StatusBarSystemEventAnimator mSystemEventAnimator;
+ private final CarrierConfigChangedListener mCarrierConfigCallback =
+ new CarrierConfigChangedListener() {
+ @Override
+ public void onCarrierConfigChanged() {
+ if (mOperatorNameViewController == null) {
+ initOperatorName();
+ } else {
+ // Already initialized, KeyguardUpdateMonitorCallback will handle the update
+ }
+ }
+ };
+
+ private final DefaultDataSubscriptionChangedListener mDefaultDataListener =
+ new DefaultDataSubscriptionChangedListener() {
+ @Override
+ public void onDefaultSubscriptionChanged(int subId) {
+ if (mOperatorNameViewController == null) {
+ initOperatorName();
+ }
+ }
+ };
+
@SuppressLint("ValidFragment")
public CollapsedStatusBarFragment(
StatusBarFragmentComponent.Factory statusBarFragmentComponentFactory,
@@ -153,6 +180,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
NetworkController networkController,
StatusBarStateController statusBarStateController,
CommandQueue commandQueue,
+ CarrierConfigTracker carrierConfigTracker,
CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger,
OperatorNameViewController.Factory operatorNameViewControllerFactory,
SecureSettings secureSettings,
@@ -172,6 +200,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
mNetworkController = networkController;
mStatusBarStateController = statusBarStateController;
mCommandQueue = commandQueue;
+ mCarrierConfigTracker = carrierConfigTracker;
mCollapsedStatusBarFragmentLogger = collapsedStatusBarFragmentLogger;
mOperatorNameViewControllerFactory = operatorNameViewControllerFactory;
mSecureSettings = secureSettings;
@@ -212,6 +241,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
initNotificationIconArea();
mSystemEventAnimator =
new StatusBarSystemEventAnimator(mSystemIconArea, getResources());
+ mCarrierConfigTracker.addCallback(mCarrierConfigCallback);
+ mCarrierConfigTracker.addDefaultDataSubscriptionChangedListener(mDefaultDataListener);
}
@VisibleForTesting
@@ -283,6 +314,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
if (mNetworkController.hasEmergencyCryptKeeperText()) {
mNetworkController.removeCallback(mSignalCallback);
}
+ mCarrierConfigTracker.removeCallback(mCarrierConfigCallback);
+ mCarrierConfigTracker.removeDataSubscriptionChangedListener(mDefaultDataListener);
}
/** Initializes views related to the notification icon area. */
@@ -569,11 +602,16 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
}
private void initOperatorName() {
- if (getResources().getBoolean(R.bool.config_showOperatorNameInStatusBar)) {
+ int subId = SubscriptionManager.getDefaultDataSubscriptionId();
+ if (mCarrierConfigTracker.getShowOperatorNameInStatusBarConfig(subId)) {
ViewStub stub = mStatusBar.findViewById(R.id.operator_name);
mOperatorNameViewController =
mOperatorNameViewControllerFactory.create((OperatorNameView) stub.inflate());
mOperatorNameViewController.init();
+ // This view should not be visible on lock-screen
+ if (mKeyguardStateController.isShowing()) {
+ hideOperatorName(false);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index 95a7316f7a58..ecaa28b0d4eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.policy;
import android.annotation.Nullable;
+import android.view.View;
import com.android.systemui.Dumpable;
import com.android.systemui.demomode.DemoMode;
@@ -24,6 +25,7 @@ import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChang
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
public interface BatteryController extends DemoMode, Dumpable,
CallbackController<BatteryStateChangeCallback> {
@@ -35,7 +37,32 @@ public interface BatteryController extends DemoMode, Dumpable,
/**
* Sets if the current device is in power save mode.
*/
- void setPowerSaveMode(boolean powerSave);
+ default void setPowerSaveMode(boolean powerSave) {
+ setPowerSaveMode(powerSave, null);
+ }
+
+ /**
+ * Sets if the current device is in power save mode.
+ *
+ * Can pass the view that triggered the request.
+ */
+ void setPowerSaveMode(boolean powerSave, @Nullable View view);
+
+ /**
+ * Gets a reference to the last view used when called {@link #setPowerSaveMode}.
+ */
+ @Nullable
+ default WeakReference<View> getLastPowerSaverStartView() {
+ return null;
+ }
+
+ /**
+ * Clears the last view used when called {@link #setPowerSaveMode}.
+ *
+ * Immediately after calling this, a call to {@link #getLastPowerSaverStartView()} should return
+ * {@code null}.
+ */
+ default void clearLastPowerSaverStartView() {}
/**
* Returns {@code true} if the device is currently plugged in.
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 9e2c478fbd69..1e71dea29eba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -28,6 +28,7 @@ import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerSaveState;
import android.util.Log;
+import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -45,8 +46,10 @@ import com.android.systemui.power.EnhancedEstimates;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
/**
* Default implementation of a {@link BatteryController}. This controller monitors for battery
@@ -85,6 +88,11 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
private Estimate mEstimate;
private boolean mFetchingEstimate = false;
+ // Use AtomicReference because we may request it from a different thread
+ // Use WeakReference because we are keeping a reference to a View that's not as long lived
+ // as this controller.
+ private AtomicReference<WeakReference<View>> mPowerSaverStartView = new AtomicReference<>();
+
@VisibleForTesting
public BatteryControllerImpl(
Context context,
@@ -141,11 +149,22 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
}
@Override
- public void setPowerSaveMode(boolean powerSave) {
+ public void setPowerSaveMode(boolean powerSave, View view) {
+ if (powerSave) mPowerSaverStartView.set(new WeakReference<>(view));
BatterySaverUtils.setPowerSaveMode(mContext, powerSave, /*needFirstTimeWarning*/ true);
}
@Override
+ public WeakReference<View> getLastPowerSaverStartView() {
+ return mPowerSaverStartView.get();
+ }
+
+ @Override
+ public void clearLastPowerSaverStartView() {
+ mPowerSaverStartView.set(null);
+ }
+
+ @Override
public void addCallback(@NonNull BatteryController.BatteryStateChangeCallback cb) {
synchronized (mChangeCallbacks) {
mChangeCallbacks.add(cb);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index fe08fb0a9d4f..217a6134fbde 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -1215,7 +1215,8 @@ public class UserSwitcherController implements Dumpable {
}
// Use broadcast instead of ShadeController, as this dialog may have started in
// another process and normal dagger bindings are not available
- mBroadcastSender.closeSystemDialogs();
+ mBroadcastSender.sendBroadcastAsUser(
+ new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), UserHandle.CURRENT);
getContext().startActivityAsUser(
CreateUserActivity.createIntentForStart(getContext()),
mUserTracker.getUserHandle());
diff --git a/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
index 14190fa752c3..5f7d74542fff 100644
--- a/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
@@ -23,43 +23,73 @@ import android.content.IntentFilter;
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
+import android.util.ArraySet;
import android.util.SparseBooleanArray;
+import androidx.annotation.NonNull;
+
+import com.android.internal.telephony.TelephonyIntents;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.policy.CallbackController;
+
+import java.util.Set;
import javax.inject.Inject;
/**
- * Tracks the Carrier Config values.
+ * Tracks CarrierConfigs for each subId, as well as the default configuration. CarrierConfigurations
+ * do not trigger a device configuration event, so any UI that relies on carrier configurations must
+ * register with the tracker to get proper updates.
+ *
+ * The tracker also listens for `TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED`
+ *
+ * @see CarrierConfigChangedListener to listen for updates
*/
@SysUISingleton
-public class CarrierConfigTracker extends BroadcastReceiver {
+public class CarrierConfigTracker
+ extends BroadcastReceiver
+ implements CallbackController<CarrierConfigTracker.CarrierConfigChangedListener> {
private final SparseBooleanArray mCallStrengthConfigs = new SparseBooleanArray();
private final SparseBooleanArray mNoCallingConfigs = new SparseBooleanArray();
private final SparseBooleanArray mCarrierProvisionsWifiMergedNetworks =
new SparseBooleanArray();
+ private final SparseBooleanArray mShowOperatorNameConfigs = new SparseBooleanArray();
private final CarrierConfigManager mCarrierConfigManager;
+ private final Set<CarrierConfigChangedListener> mListeners = new ArraySet<>();
+ private final Set<DefaultDataSubscriptionChangedListener> mDataListeners =
+ new ArraySet<>();
private boolean mDefaultCallStrengthConfigLoaded;
private boolean mDefaultCallStrengthConfig;
private boolean mDefaultNoCallingConfigLoaded;
private boolean mDefaultNoCallingConfig;
private boolean mDefaultCarrierProvisionsWifiMergedNetworksLoaded;
private boolean mDefaultCarrierProvisionsWifiMergedNetworks;
+ private boolean mDefaultShowOperatorNameConfigLoaded;
+ private boolean mDefaultShowOperatorNameConfig;
@Inject
- public CarrierConfigTracker(Context context, BroadcastDispatcher broadcastDispatcher) {
- mCarrierConfigManager = context.getSystemService(CarrierConfigManager.class);
- broadcastDispatcher.registerReceiver(
- this, new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
+ public CarrierConfigTracker(
+ CarrierConfigManager carrierConfigManager,
+ BroadcastDispatcher broadcastDispatcher) {
+ mCarrierConfigManager = carrierConfigManager;
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+ filter.addAction(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
+ broadcastDispatcher.registerReceiver(this, filter);
}
@Override
public void onReceive(Context context, Intent intent) {
- if (!CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
- return;
+ String action = intent.getAction();
+ if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(action)) {
+ updateFromNewCarrierConfig(intent);
+ } else if (TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED.equals(action)) {
+ updateDefaultDataSubscription(intent);
}
+ }
+ private void updateFromNewCarrierConfig(Intent intent) {
final int subId = intent.getIntExtra(
CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
@@ -84,6 +114,29 @@ public class CarrierConfigTracker extends BroadcastReceiver {
mCarrierProvisionsWifiMergedNetworks.put(subId, config.getBoolean(
CarrierConfigManager.KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL));
}
+ synchronized (mShowOperatorNameConfigs) {
+ mShowOperatorNameConfigs.put(subId, config.getBoolean(
+ CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL));
+ }
+
+ notifyCarrierConfigChanged();
+ }
+
+ private void updateDefaultDataSubscription(Intent intent) {
+ int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, -1);
+ notifyDefaultDataSubscriptionChanged(subId);
+ }
+
+ private void notifyCarrierConfigChanged() {
+ for (CarrierConfigChangedListener l : mListeners) {
+ l.onCarrierConfigChanged();
+ }
+ }
+
+ private void notifyDefaultDataSubscriptionChanged(int subId) {
+ for (DefaultDataSubscriptionChangedListener l : mDataListeners) {
+ l.onDefaultSubscriptionChanged(subId);
+ }
}
/**
@@ -139,4 +192,73 @@ public class CarrierConfigTracker extends BroadcastReceiver {
}
return mDefaultCarrierProvisionsWifiMergedNetworks;
}
+
+ /**
+ * Returns the KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL value for the default config
+ */
+ public boolean getShowOperatorNameInStatusBarConfigDefault() {
+ if (!mDefaultShowOperatorNameConfigLoaded) {
+ mDefaultShowOperatorNameConfig = CarrierConfigManager.getDefaultConfig().getBoolean(
+ CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL);
+ mDefaultShowOperatorNameConfigLoaded = true;
+ }
+
+ return mDefaultShowOperatorNameConfig;
+ }
+
+ /**
+ * Returns the KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL value for the given subId, or the
+ * default value if no override exists
+ *
+ * @param subId the subscription id for which to query the config
+ */
+ public boolean getShowOperatorNameInStatusBarConfig(int subId) {
+ if (mShowOperatorNameConfigs.indexOfKey(subId) >= 0) {
+ return mShowOperatorNameConfigs.get(subId);
+ } else {
+ return getShowOperatorNameInStatusBarConfigDefault();
+ }
+ }
+
+ @Override
+ public void addCallback(@NonNull CarrierConfigChangedListener listener) {
+ mListeners.add(listener);
+ }
+
+ @Override
+ public void removeCallback(@NonNull CarrierConfigChangedListener listener) {
+ mListeners.remove(listener);
+ }
+
+ /** */
+ public void addDefaultDataSubscriptionChangedListener(
+ @NonNull DefaultDataSubscriptionChangedListener listener) {
+ mDataListeners.add(listener);
+ }
+
+ /** */
+ public void removeDataSubscriptionChangedListener(
+ DefaultDataSubscriptionChangedListener listener) {
+ mDataListeners.remove(listener);
+ }
+
+ /**
+ * Called when carrier config changes
+ */
+ public interface CarrierConfigChangedListener {
+ /** */
+ void onCarrierConfigChanged();
+ }
+
+ /**
+ * Called when the default data subscription changes. Listeners may want to query
+ * subId-dependent configuration values when this event happens
+ */
+ public interface DefaultDataSubscriptionChangedListener {
+ /**
+ * @param subId the new default data subscription id per
+ * {@link SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX}
+ */
+ void onDefaultSubscriptionChanged(int subId);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/DualHeightHorizontalLinearLayout.kt b/packages/SystemUI/src/com/android/systemui/util/DualHeightHorizontalLinearLayout.kt
deleted file mode 100644
index cfceefa2006c..000000000000
--- a/packages/SystemUI/src/com/android/systemui/util/DualHeightHorizontalLinearLayout.kt
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2021 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.util
-
-import android.content.Context
-import android.content.res.Configuration
-import android.util.AttributeSet
-import android.util.DisplayMetrics
-import android.util.TypedValue
-import android.widget.LinearLayout
-import android.widget.TextView
-import com.android.systemui.R
-
-/**
- * Horizontal [LinearLayout] to contain some text.
- *
- * The height of this container can alternate between two different heights, depending on whether
- * the text takes one line or more.
- *
- * When the text takes multiple lines, it will use the values in the regular attributes (`padding`,
- * `layout_height`). The single line behavior must be set in XML.
- *
- * XML attributes for single line behavior:
- * * `systemui:textViewId`: set the id for the [TextView] that determines the height of the
- * container
- * * `systemui:singleLineHeight`: sets the height of the view when the text takes up only one line.
- * By default, it will use [getMinimumHeight].
- * * `systemui:singleLineVerticalPadding`: sets the padding (top and bottom) when then text takes up
- * only one line. By default, it is 0.
- *
- * All dimensions are updated when configuration changes.
- */
-class DualHeightHorizontalLinearLayout @JvmOverloads constructor(
- context: Context,
- attrs: AttributeSet? = null,
- defStyleAttrs: Int = 0,
- defStyleRes: Int = 0
-) : LinearLayout(context, attrs, defStyleAttrs, defStyleRes) {
-
- private val singleLineHeightValue: TypedValue?
- private var singleLineHeightPx = 0
-
- private val singleLineVerticalPaddingValue: TypedValue?
- private var singleLineVerticalPaddingPx = 0
-
- private val textViewId: Int
- private var textView: TextView? = null
-
- private val displayMetrics: DisplayMetrics
- get() = context.resources.displayMetrics
-
- private var initialPadding = mPaddingTop // All vertical padding is the same
-
- private var originalMaxLines = 1
- var alwaysSingleLine: Boolean = false
- set(value) {
- field = value
- if (field) {
- textView?.setSingleLine()
- } else {
- textView?.maxLines = originalMaxLines
- }
- }
-
- init {
- if (orientation != HORIZONTAL) {
- throw IllegalStateException("This view should always have horizontal orientation")
- }
-
- val ta = context.obtainStyledAttributes(
- attrs,
- R.styleable.DualHeightHorizontalLinearLayout, defStyleAttrs, defStyleRes
- )
-
- val tempHeight = TypedValue()
- singleLineHeightValue = if (
- ta.hasValue(R.styleable.DualHeightHorizontalLinearLayout_singleLineHeight)
- ) {
- ta.getValue(R.styleable.DualHeightHorizontalLinearLayout_singleLineHeight, tempHeight)
- tempHeight
- } else {
- null
- }
-
- val tempPadding = TypedValue()
- singleLineVerticalPaddingValue = if (
- ta.hasValue(R.styleable.DualHeightHorizontalLinearLayout_singleLineVerticalPadding)
- ) {
- ta.getValue(
- R.styleable.DualHeightHorizontalLinearLayout_singleLineVerticalPadding,
- tempPadding
- )
- tempPadding
- } else {
- null
- }
-
- textViewId = ta.getResourceId(R.styleable.DualHeightHorizontalLinearLayout_textViewId, 0)
-
- ta.recycle()
- }
-
- init {
- updateResources()
- }
-
- override fun setPadding(left: Int, top: Int, right: Int, bottom: Int) {
- super.setPadding(left, top, right, bottom)
- initialPadding = top
- }
-
- override fun setPaddingRelative(start: Int, top: Int, end: Int, bottom: Int) {
- super.setPaddingRelative(start, top, end, bottom)
- initialPadding = top
- }
-
- override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec)
- textView?.let { tv ->
- if (tv.lineCount < 2 || alwaysSingleLine) {
- setMeasuredDimension(measuredWidth, singleLineHeightPx)
- mPaddingBottom = 0
- mPaddingTop = 0
- } else {
- mPaddingBottom = initialPadding
- mPaddingTop = initialPadding
- }
- }
- }
-
- override fun onFinishInflate() {
- super.onFinishInflate()
- textView = findViewById<TextView>(textViewId)?.also {
- originalMaxLines = it.maxLines
- }
- }
-
- override fun onConfigurationChanged(newConfig: Configuration?) {
- super.onConfigurationChanged(newConfig)
- updateResources()
- }
-
- override fun setOrientation(orientation: Int) {
- if (orientation == VERTICAL) {
- throw IllegalStateException("This view should always have horizontal orientation")
- }
- super.setOrientation(orientation)
- }
-
- private fun updateResources() {
- updateDimensionValue(singleLineHeightValue, minimumHeight, ::singleLineHeightPx::set)
- updateDimensionValue(singleLineVerticalPaddingValue, 0, ::singleLineVerticalPaddingPx::set)
- }
-
- private inline fun updateDimensionValue(
- tv: TypedValue?,
- defaultValue: Int,
- propertySetter: (Int) -> Unit
- ) {
- val value = tv?.let {
- if (it.resourceId != 0) {
- context.resources.getDimensionPixelSize(it.resourceId)
- } else {
- it.getDimension(displayMetrics).toInt()
- }
- } ?: defaultValue
- propertySetter(value)
- }
-} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 4d34aa38b3cd..b70220d09749 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -576,7 +576,9 @@ public class BubblesManager implements Dumpable {
@Override
public void onEntryRemoved(NotificationEntry entry,
@NotifCollection.CancellationReason int reason) {
- BubblesManager.this.onEntryRemoved(entry);
+ if (reason == REASON_APP_CANCEL || reason == REASON_APP_CANCEL_ALL) {
+ BubblesManager.this.onEntryRemoved(entry);
+ }
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
index 4cca38a6e5a8..cc606de1b0b9 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
@@ -88,6 +88,7 @@ private fun faceModel(user: Int) = KeyguardFaceListenModel(
bouncerFullyShown = false,
faceAuthenticated = false,
faceDisabled = false,
+ goingToSleep = false,
keyguardAwake = false,
keyguardGoingAway = false,
listeningForFaceAssistant = false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
index 26296d67c71a..67fe0445e225 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
@@ -52,6 +52,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
@Mock lateinit var panel: MediaControlPanel
@Mock lateinit var visualStabilityProvider: VisualStabilityProvider
@Mock lateinit var mediaHostStatesManager: MediaHostStatesManager
+ @Mock lateinit var mediaHostState: MediaHostState
@Mock lateinit var activityStarter: ActivityStarter
@Mock @Main private lateinit var executor: DelayableExecutor
@Mock lateinit var mediaDataManager: MediaDataManager
@@ -188,4 +189,40 @@ class MediaCarouselControllerTest : SysuiTestCase() {
verify(logger).logCarouselSettings()
}
+
+ @Test
+ fun testLocationChangeQs_logged() {
+ mediaCarouselController.onDesiredLocationChanged(
+ MediaHierarchyManager.LOCATION_QS,
+ mediaHostState,
+ animate = false)
+ verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QS)
+ }
+
+ @Test
+ fun testLocationChangeQqs_logged() {
+ mediaCarouselController.onDesiredLocationChanged(
+ MediaHierarchyManager.LOCATION_QQS,
+ mediaHostState,
+ animate = false)
+ verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QQS)
+ }
+
+ @Test
+ fun testLocationChangeLockscreen_logged() {
+ mediaCarouselController.onDesiredLocationChanged(
+ MediaHierarchyManager.LOCATION_LOCKSCREEN,
+ mediaHostState,
+ animate = false)
+ verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_LOCKSCREEN)
+ }
+
+ @Test
+ fun testLocationChangeDream_logged() {
+ mediaCarouselController.onDesiredLocationChanged(
+ MediaHierarchyManager.LOCATION_DREAM_OVERLAY,
+ mediaHostState,
+ animate = false)
+ verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_DREAM_OVERLAY)
+ }
} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index f2e3edbc0761..538a9c763438 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.media
+import android.app.PendingIntent
import org.mockito.Mockito.`when` as whenever
import android.content.Intent
import android.graphics.Color
@@ -104,6 +105,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Mock private lateinit var mediaOutputDialogFactory: MediaOutputDialogFactory
@Mock private lateinit var mediaCarouselController: MediaCarouselController
@Mock private lateinit var falsingManager: FalsingManager
+ @Mock private lateinit var transitionParent: ViewGroup
private lateinit var appIcon: ImageView
private lateinit var albumView: ImageView
private lateinit var titleText: TextView
@@ -241,6 +243,10 @@ public class MediaControlPanelTest : SysuiTestCase() {
whenever(viewHolder.seamlessText).thenReturn(seamlessText)
whenever(viewHolder.seekBar).thenReturn(seekBar)
+ // Transition View
+ whenever(view.parent).thenReturn(transitionParent)
+ whenever(view.rootView).thenReturn(transitionParent)
+
// Action buttons
whenever(viewHolder.actionPlayPause).thenReturn(actionPlayPause)
whenever(viewHolder.getAction(R.id.actionPlayPause)).thenReturn(actionPlayPause)
@@ -749,6 +755,20 @@ public class MediaControlPanelTest : SysuiTestCase() {
}
@Test
+ fun tapContentView_isLogged() {
+ val pendingIntent = mock(PendingIntent::class.java)
+ val captor = ArgumentCaptor.forClass(View.OnClickListener::class.java)
+ val data = mediaData.copy(clickIntent = pendingIntent)
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(data, KEY)
+ verify(viewHolder.player).setOnClickListener(captor.capture())
+
+ captor.value.onClick(viewHolder.player)
+
+ verify(logger).logTapContentView(anyInt(), eq(PACKAGE), eq(instanceId))
+ }
+
+ @Test
fun logSeek() {
player.attachPlayer(viewHolder)
player.bindPlayer(mediaData, KEY)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index b9ff8775f2b8..1921cb624fde 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -726,6 +726,25 @@ class MediaDataManagerTest : SysuiTestCase() {
}
@Test
+ fun testPlaybackActions_connecting() {
+ whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ val stateActions = PlaybackState.ACTION_PLAY
+ val stateBuilder = PlaybackState.Builder()
+ .setState(PlaybackState.STATE_BUFFERING, 0, 10f)
+ .setActions(stateActions)
+ whenever(controller.playbackState).thenReturn(stateBuilder.build())
+
+ addNotificationAndLoad()
+
+ assertThat(mediaDataCaptor.value!!.semanticActions).isNotNull()
+ val actions = mediaDataCaptor.value!!.semanticActions!!
+
+ assertThat(actions.playOrPause).isNotNull()
+ assertThat(actions.playOrPause!!.contentDescription).isEqualTo(
+ context.getString(R.string.controls_media_button_connecting))
+ }
+
+ @Test
fun testPlaybackActions_reservedSpace() {
val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
index e6f48ecd37de..10eeb11faa05 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
@@ -265,20 +265,58 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
}
@Test
- fun onAboutToConnectDeviceChangedWithNonNullParams() {
+ fun onAboutToConnectDeviceAdded_findsDeviceInfoFromAddress() {
manager.onMediaDataLoaded(KEY, null, mediaData)
// Run and reset the executors and listeners so we only focus on new events.
fakeBgExecutor.runAllReady()
fakeFgExecutor.runAllReady()
reset(listener)
+ // Ensure we'll get device info when using the address
+ val fullMediaDevice = mock(MediaDevice::class.java)
+ val address = "fakeAddress"
+ val nameFromDevice = "nameFromDevice"
+ val iconFromDevice = mock(Drawable::class.java)
+ whenever(lmm.getMediaDeviceById(eq(address))).thenReturn(fullMediaDevice)
+ whenever(fullMediaDevice.name).thenReturn(nameFromDevice)
+ whenever(fullMediaDevice.iconWithoutBackground).thenReturn(iconFromDevice)
+
+ // WHEN the about-to-connect device changes to non-null
val deviceCallback = captureCallback()
+ val nameFromParam = "nameFromParam"
+ val iconFromParam = mock(Drawable::class.java)
+ deviceCallback.onAboutToConnectDeviceAdded(address, nameFromParam, iconFromParam)
+ assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1)
+
+ // THEN the about-to-connect device based on the address is returned
+ val data = captureDeviceData(KEY)
+ assertThat(data.enabled).isTrue()
+ assertThat(data.name).isEqualTo(nameFromDevice)
+ assertThat(data.name).isNotEqualTo(nameFromParam)
+ assertThat(data.icon).isEqualTo(iconFromDevice)
+ assertThat(data.icon).isNotEqualTo(iconFromParam)
+ }
+
+ @Test
+ fun onAboutToConnectDeviceAdded_cantFindDeviceInfoFromAddress() {
+ manager.onMediaDataLoaded(KEY, null, mediaData)
+ // Run and reset the executors and listeners so we only focus on new events.
+ fakeBgExecutor.runAllReady()
+ fakeFgExecutor.runAllReady()
+ reset(listener)
+
+ // Ensure we can't get device info based on the address
+ val address = "fakeAddress"
+ whenever(lmm.getMediaDeviceById(eq(address))).thenReturn(null)
+
// WHEN the about-to-connect device changes to non-null
+ val deviceCallback = captureCallback()
val name = "AboutToConnectDeviceName"
val mockIcon = mock(Drawable::class.java)
- deviceCallback.onAboutToConnectDeviceChanged(name, mockIcon)
+ deviceCallback.onAboutToConnectDeviceAdded(address, name, mockIcon)
assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1)
- // THEN the about-to-connect device is returned
+
+ // THEN the about-to-connect device based on the parameters is returned
val data = captureDeviceData(KEY)
assertThat(data.enabled).isTrue()
assertThat(data.name).isEqualTo(name)
@@ -286,21 +324,21 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
}
@Test
- fun onAboutToConnectDeviceChangedWithNullParams() {
+ fun onAboutToConnectDeviceAddedThenRemoved_usesNormalDevice() {
manager.onMediaDataLoaded(KEY, null, mediaData)
fakeBgExecutor.runAllReady()
val deviceCallback = captureCallback()
// First set a non-null about-to-connect device
- deviceCallback.onAboutToConnectDeviceChanged(
- "AboutToConnectDeviceName", mock(Drawable::class.java)
+ deviceCallback.onAboutToConnectDeviceAdded(
+ "fakeAddress", "AboutToConnectDeviceName", mock(Drawable::class.java)
)
// Run and reset the executors and listeners so we only focus on new events.
fakeBgExecutor.runAllReady()
fakeFgExecutor.runAllReady()
reset(listener)
- // WHEN the about-to-connect device changes to null
- deviceCallback.onAboutToConnectDeviceChanged(null, null)
+ // WHEN hasDevice switches to false
+ deviceCallback.onAboutToConnectDeviceRemoved()
assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1)
// THEN the normal device is returned
val data = captureDeviceData(KEY)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt
index 88c451499d21..27c039dcf29c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt
@@ -24,7 +24,7 @@ import android.media.AudioDeviceAttributes
import android.media.AudioDeviceInfo
import android.media.AudioManager
import android.media.AudioManager.MuteAwaitConnectionCallback.EVENT_CONNECTION
-import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.filters.SmallTest
import com.android.settingslib.media.DeviceIconUtil
import com.android.settingslib.media.LocalMediaManager
import com.android.systemui.R
@@ -95,7 +95,7 @@ class MediaMuteAwaitConnectionManagerTest : SysuiTestCase() {
muteAwaitConnectionManager.startListening()
- verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
+ verify(localMediaManager, never()).dispatchAboutToConnectDeviceAdded(any(), any(), any())
}
@Test
@@ -104,7 +104,9 @@ class MediaMuteAwaitConnectionManagerTest : SysuiTestCase() {
muteAwaitConnectionManager.startListening()
- verify(localMediaManager).dispatchAboutToConnectDeviceChanged(eq(DEVICE_NAME), eq(icon))
+ verify(localMediaManager).dispatchAboutToConnectDeviceAdded(
+ eq(DEVICE_ADDRESS), eq(DEVICE_NAME), eq(icon)
+ )
}
@Test
@@ -114,7 +116,7 @@ class MediaMuteAwaitConnectionManagerTest : SysuiTestCase() {
muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_UNKNOWN))
- verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
+ verify(localMediaManager, never()).dispatchAboutToConnectDeviceAdded(any(), any(), any())
}
@Test
@@ -125,7 +127,9 @@ class MediaMuteAwaitConnectionManagerTest : SysuiTestCase() {
muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_MEDIA))
- verify(localMediaManager).dispatchAboutToConnectDeviceChanged(eq(DEVICE_NAME), eq(icon))
+ verify(localMediaManager).dispatchAboutToConnectDeviceAdded(
+ eq(DEVICE_ADDRESS), eq(DEVICE_NAME), eq(icon)
+ )
}
@Test
@@ -135,7 +139,7 @@ class MediaMuteAwaitConnectionManagerTest : SysuiTestCase() {
muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, DEVICE, intArrayOf(USAGE_MEDIA))
- verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
+ verify(localMediaManager, never()).dispatchAboutToConnectDeviceAdded(any(), any(), any())
}
@Test
@@ -155,7 +159,7 @@ class MediaMuteAwaitConnectionManagerTest : SysuiTestCase() {
)
muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, otherDevice, intArrayOf(USAGE_MEDIA))
- verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
+ verify(localMediaManager, never()).dispatchAboutToConnectDeviceAdded(any(), any(), any())
}
@Test
@@ -167,7 +171,7 @@ class MediaMuteAwaitConnectionManagerTest : SysuiTestCase() {
muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, DEVICE, intArrayOf(USAGE_UNKNOWN))
- verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
+ verify(localMediaManager, never()).dispatchAboutToConnectDeviceAdded(any(), any(), any())
}
@Test
@@ -179,7 +183,7 @@ class MediaMuteAwaitConnectionManagerTest : SysuiTestCase() {
muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, DEVICE, intArrayOf(USAGE_MEDIA))
- verify(localMediaManager).dispatchAboutToConnectDeviceChanged(eq(null), eq(null))
+ verify(localMediaManager).dispatchAboutToConnectDeviceRemoved()
}
private fun getMuteAwaitListener(): AudioManager.MuteAwaitConnectionCallback {
@@ -191,11 +195,12 @@ class MediaMuteAwaitConnectionManagerTest : SysuiTestCase() {
}
}
+private const val DEVICE_ADDRESS = "DeviceAddress"
private const val DEVICE_NAME = "DeviceName"
private val DEVICE = AudioDeviceAttributes(
AudioDeviceAttributes.ROLE_OUTPUT,
AudioDeviceInfo.TYPE_USB_HEADSET,
- "address",
+ DEVICE_ADDRESS,
DEVICE_NAME,
listOf(),
listOf(),
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 a156820ad141..1ffa9dd57aa9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
@@ -25,29 +25,48 @@ import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyString;
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;
import android.app.Notification;
import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.os.BatteryManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.UserHandle;
import android.test.suitebuilder.annotation.SmallTest;
-
-import androidx.test.runner.AndroidJUnit4;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.settingslib.fuelgauge.BatterySaverUtils;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.NotificationChannels;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.lang.ref.WeakReference;
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
public class PowerNotificationWarningsTest extends SysuiTestCase {
public static final String FORMATTED_45M = "0h 45m";
@@ -55,14 +74,34 @@ public class PowerNotificationWarningsTest extends SysuiTestCase {
private final NotificationManager mMockNotificationManager = mock(NotificationManager.class);
private PowerNotificationWarnings mPowerNotificationWarnings;
+ @Mock
+ private BatteryController mBatteryController;
+ @Mock
+ private DialogLaunchAnimator mDialogLaunchAnimator;
+ @Mock
+ private View mView;
+
+ private BroadcastReceiver mReceiver;
+
@Before
public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ Context wrapper = new ContextWrapper(mContext) {
+ @Override
+ public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
+ IntentFilter filter, String broadcastPermission, Handler scheduler, int flags) {
+ mReceiver = receiver;
+ return null;
+ }
+ };
+
// Test Instance.
mContext.addMockSystemService(NotificationManager.class, mMockNotificationManager);
ActivityStarter starter = mDependency.injectMockDependency(ActivityStarter.class);
BroadcastSender broadcastSender = mDependency.injectMockDependency(BroadcastSender.class);
- mPowerNotificationWarnings = new PowerNotificationWarnings(mContext, starter,
- broadcastSender);
+ mPowerNotificationWarnings = new PowerNotificationWarnings(wrapper, starter,
+ broadcastSender, () -> mBatteryController, mDialogLaunchAnimator);
BatteryStateSnapshot snapshot = new BatteryStateSnapshot(100, false, false, 1,
BatteryManager.BATTERY_HEALTH_GOOD, 5, 15);
mPowerNotificationWarnings.updateSnapshot(snapshot);
@@ -168,4 +207,52 @@ public class PowerNotificationWarningsTest extends SysuiTestCase {
mPowerNotificationWarnings.mUsbHighTempDialog.dismiss();
}
+
+ @Test
+ public void testDialogStartedFromLauncher_viewVisible() {
+ when(mBatteryController.getLastPowerSaverStartView())
+ .thenReturn(new WeakReference<>(mView));
+ when(mView.isAggregatedVisible()).thenReturn(true);
+
+ Intent intent = new Intent(BatterySaverUtils.ACTION_SHOW_START_SAVER_CONFIRMATION);
+ intent.putExtras(new Bundle());
+
+ mReceiver.onReceive(mContext, intent);
+
+ verify(mDialogLaunchAnimator).showFromView(any(), eq(mView));
+
+ mPowerNotificationWarnings.getSaverConfirmationDialog().dismiss();
+ }
+
+ @Test
+ public void testDialogStartedNotFromLauncher_viewNotVisible() {
+ when(mBatteryController.getLastPowerSaverStartView())
+ .thenReturn(new WeakReference<>(mView));
+ when(mView.isAggregatedVisible()).thenReturn(false);
+
+ Intent intent = new Intent(BatterySaverUtils.ACTION_SHOW_START_SAVER_CONFIRMATION);
+ intent.putExtras(new Bundle());
+
+ mReceiver.onReceive(mContext, intent);
+
+ verify(mDialogLaunchAnimator, never()).showFromView(any(), any());
+
+ assertThat(mPowerNotificationWarnings.getSaverConfirmationDialog().isShowing()).isTrue();
+ mPowerNotificationWarnings.getSaverConfirmationDialog().dismiss();
+ }
+
+ @Test
+ public void testDialogShownNotFromLauncher() {
+ when(mBatteryController.getLastPowerSaverStartView()).thenReturn(null);
+
+ Intent intent = new Intent(BatterySaverUtils.ACTION_SHOW_START_SAVER_CONFIRMATION);
+ intent.putExtras(new Bundle());
+
+ mReceiver.onReceive(mContext, intent);
+
+ verify(mDialogLaunchAnimator, never()).showFromView(any(), any());
+
+ assertThat(mPowerNotificationWarnings.getSaverConfirmationDialog().isShowing()).isTrue();
+ mPowerNotificationWarnings.getSaverConfirmationDialog().dismiss();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
index 7b17c36aaf16..35d0024b3bf1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
@@ -8,20 +8,20 @@ import android.testing.TestableLooper
import android.testing.ViewUtils
import android.view.LayoutInflater
import android.view.View
+import android.view.ViewGroup
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.UiEventLogger
import com.android.internal.logging.testing.FakeMetricsLogger
import com.android.systemui.R
import com.android.systemui.classifier.FalsingManagerFake
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.globalactions.GlobalActionsDialogLite
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.MultiUserSwitchController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.UserInfoController
+import com.android.systemui.util.mockito.capture
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.utils.leaks.LeakCheckedTest
import com.google.common.truth.Truth.assertThat
@@ -29,10 +29,14 @@ import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
@@ -65,11 +69,12 @@ class FooterActionsControllerTest : LeakCheckedTest() {
@Mock
private lateinit var uiEventLogger: UiEventLogger
@Mock
- private lateinit var featureFlags: FeatureFlags
- @Mock
private lateinit var securityFooterController: QSSecurityFooter
@Mock
private lateinit var fgsManagerController: QSFgsManagerFooter
+ @Captor
+ private lateinit var visibilityChangedCaptor:
+ ArgumentCaptor<VisibilityChangedDispatcher.OnVisibilityChangedListener>
private lateinit var controller: FooterActionsController
@@ -78,6 +83,8 @@ class FooterActionsControllerTest : LeakCheckedTest() {
private val falsingManager: FalsingManagerFake = FalsingManagerFake()
private lateinit var testableLooper: TestableLooper
private lateinit var fakeSettings: FakeSettings
+ private lateinit var securityFooter: View
+ private lateinit var fgsFooter: View
@Before
fun setUp() {
@@ -87,9 +94,14 @@ class FooterActionsControllerTest : LeakCheckedTest() {
whenever(multiUserSwitchControllerFactory.create(any()))
.thenReturn(multiUserSwitchController)
- whenever(featureFlags.isEnabled(Flags.NEW_FOOTER)).thenReturn(false)
whenever(globalActionsDialogProvider.get()).thenReturn(globalActionsDialog)
+ securityFooter = View(mContext)
+ fgsFooter = View(mContext)
+
+ whenever(securityFooterController.view).thenReturn(securityFooter)
+ whenever(fgsManagerController.view).thenReturn(fgsFooter)
+
view = inflateView()
controller = constructFooterActionsController(view)
@@ -107,6 +119,13 @@ class FooterActionsControllerTest : LeakCheckedTest() {
}
@Test
+ fun testInitializesControllers() {
+ verify(multiUserSwitchController).init()
+ verify(fgsManagerController).init()
+ verify(securityFooterController).init()
+ }
+
+ @Test
fun testLogPowerMenuClick() {
controller.visible = true
falsingManager.setFalseTap(false)
@@ -182,6 +201,10 @@ class FooterActionsControllerTest : LeakCheckedTest() {
@Test
fun testCleanUpGAD() {
reset(globalActionsDialogProvider)
+ // We are creating a new controller, so detach the views from it
+ (securityFooter.parent as ViewGroup).removeView(securityFooter)
+ (fgsFooter.parent as ViewGroup).removeView(fgsFooter)
+
whenever(globalActionsDialogProvider.get()).thenReturn(globalActionsDialog)
val view = inflateView()
controller = constructFooterActionsController(view)
@@ -198,6 +221,80 @@ class FooterActionsControllerTest : LeakCheckedTest() {
verify(globalActionsDialog).destroy()
}
+ @Test
+ fun testSeparatorVisibility_noneVisible_gone() {
+ verify(securityFooterController)
+ .setOnVisibilityChangedListener(capture(visibilityChangedCaptor))
+ val listener = visibilityChangedCaptor.value
+ val separator = controller.securityFootersSeparator
+
+ setVisibilities(securityFooterVisible = false, fgsFooterVisible = false, listener)
+ assertThat(separator.visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun testSeparatorVisibility_onlySecurityFooterVisible_gone() {
+ verify(securityFooterController)
+ .setOnVisibilityChangedListener(capture(visibilityChangedCaptor))
+ val listener = visibilityChangedCaptor.value
+ val separator = controller.securityFootersSeparator
+
+ setVisibilities(securityFooterVisible = true, fgsFooterVisible = false, listener)
+ assertThat(separator.visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun testSeparatorVisibility_onlyFgsFooterVisible_gone() {
+ verify(securityFooterController)
+ .setOnVisibilityChangedListener(capture(visibilityChangedCaptor))
+ val listener = visibilityChangedCaptor.value
+ val separator = controller.securityFootersSeparator
+
+ setVisibilities(securityFooterVisible = false, fgsFooterVisible = true, listener)
+ assertThat(separator.visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun testSeparatorVisibility_bothVisible_visible() {
+ verify(securityFooterController)
+ .setOnVisibilityChangedListener(capture(visibilityChangedCaptor))
+ val listener = visibilityChangedCaptor.value
+ val separator = controller.securityFootersSeparator
+
+ setVisibilities(securityFooterVisible = true, fgsFooterVisible = true, listener)
+ assertThat(separator.visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ fun testFgsFooterCollapsed() {
+ verify(securityFooterController)
+ .setOnVisibilityChangedListener(capture(visibilityChangedCaptor))
+ val listener = visibilityChangedCaptor.value
+
+ val booleanCaptor = ArgumentCaptor.forClass(Boolean::class.java)
+
+ clearInvocations(fgsManagerController)
+ setVisibilities(securityFooterVisible = false, fgsFooterVisible = true, listener)
+ verify(fgsManagerController, atLeastOnce()).setCollapsed(capture(booleanCaptor))
+ assertThat(booleanCaptor.allValues.last()).isFalse()
+
+ clearInvocations(fgsManagerController)
+ setVisibilities(securityFooterVisible = true, fgsFooterVisible = true, listener)
+ verify(fgsManagerController, atLeastOnce()).setCollapsed(capture(booleanCaptor))
+ assertThat(booleanCaptor.allValues.last()).isTrue()
+ }
+
+ private fun setVisibilities(
+ securityFooterVisible: Boolean,
+ fgsFooterVisible: Boolean,
+ listener: VisibilityChangedDispatcher.OnVisibilityChangedListener
+ ) {
+ securityFooter.visibility = if (securityFooterVisible) View.VISIBLE else View.GONE
+ listener.onVisibilityChanged(securityFooter.visibility)
+ fgsFooter.visibility = if (fgsFooterVisible) View.VISIBLE else View.GONE
+ listener.onVisibilityChanged(fgsFooter.visibility)
+ }
+
private fun inflateView(): FooterActionsView {
return LayoutInflater.from(context)
.inflate(R.layout.footer_actions, null) as FooterActionsView
@@ -208,6 +305,6 @@ class FooterActionsControllerTest : LeakCheckedTest() {
activityStarter, userManager, userTracker, userInfoController,
deviceProvisionedController, securityFooterController, fgsManagerController,
falsingManager, metricsLogger, globalActionsDialogProvider, uiEventLogger,
- showPMLiteButton = true, fakeSettings, Handler(testableLooper.looper), featureFlags)
+ showPMLiteButton = true, fakeSettings, Handler(testableLooper.looper))
}
} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt
index bf82e90e88f6..489c8c86028e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt
@@ -59,22 +59,14 @@ class QSContainerImplTest : SysuiTestCase() {
fun testContainerBottomPadding() {
qsContainer.updateResources(
qsPanelController,
- quickStatusBarHeaderController,
- /* newFooter */ false
- )
- verify(qsPanelContainer).setPaddingRelative(anyInt(), anyInt(), anyInt(), eq(0))
-
- qsContainer.updateResources(
- qsPanelController,
- quickStatusBarHeaderController,
- /* newFooter */ true
+ quickStatusBarHeaderController
)
verify(qsPanelContainer)
.setPaddingRelative(
anyInt(),
anyInt(),
anyInt(),
- eq(mContext.resources.getDimensionPixelSize(R.dimen.new_footer_height))
+ eq(mContext.resources.getDimensionPixelSize(R.dimen.footer_actions_height))
)
}
} \ No newline at end of file
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 58a070db9a95..689de50d5b4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -6,7 +6,6 @@ import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
import com.android.systemui.media.MediaHost
import com.android.systemui.media.MediaHostState
import com.android.systemui.plugins.FalsingManager
@@ -35,8 +34,6 @@ import org.mockito.Mockito.`when` as whenever
class QSPanelControllerTest : SysuiTestCase() {
@Mock private lateinit var qsPanel: QSPanel
- @Mock private lateinit var qsFgsManagerFooter: QSFgsManagerFooter
- @Mock private lateinit var qsSecurityFooter: QSSecurityFooter
@Mock private lateinit var tunerService: TunerService
@Mock private lateinit var qsTileHost: QSTileHost
@Mock private lateinit var qsCustomizerController: QSCustomizerController
@@ -50,7 +47,6 @@ class QSPanelControllerTest : SysuiTestCase() {
@Mock private lateinit var brightnessSlider: BrightnessSliderController
@Mock private lateinit var brightnessSliderFactory: BrightnessSliderController.Factory
@Mock private lateinit var falsingManager: FalsingManager
- @Mock private lateinit var featureFlags: FeatureFlags
@Mock private lateinit var mediaHost: MediaHost
@Mock private lateinit var tile: QSTile
@Mock private lateinit var otherTile: QSTile
@@ -69,8 +65,6 @@ class QSPanelControllerTest : SysuiTestCase() {
controller = QSPanelController(
qsPanel,
- qsFgsManagerFooter,
- qsSecurityFooter,
tunerService,
qsTileHost,
qsCustomizerController,
@@ -84,7 +78,6 @@ class QSPanelControllerTest : SysuiTestCase() {
brightnessControllerFactory,
brightnessSliderFactory,
falsingManager,
- featureFlags,
statusBarKeyguardViewManager
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
index ba02a828d337..e237a5ce03fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
@@ -13,9 +13,6 @@
*/
package com.android.systemui.qs
-import android.content.res.Configuration
-import android.content.res.Configuration.ORIENTATION_LANDSCAPE
-import android.content.res.Configuration.ORIENTATION_PORTRAIT
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
@@ -59,8 +56,6 @@ class QSPanelTest : SysuiTestCase() {
mFooter = LinearLayout(mContext).apply { id = R.id.qs_footer }
mQsPanel.addView(mFooter)
mQsPanel.onFinishInflate()
- mQsPanel.setSecurityFooter(View(mContext), false)
- mQsPanel.setHeaderContainer(LinearLayout(mContext))
// Provides a parent with non-zero size for QSPanel
mParentView = FrameLayout(mContext).apply {
addView(mQsPanel)
@@ -69,49 +64,6 @@ class QSPanelTest : SysuiTestCase() {
}
@Test
- fun testSecurityFooter_appearsOnBottomOnSplitShade() {
- mQsPanel.onConfigurationChanged(getNewOrientationConfig(ORIENTATION_LANDSCAPE))
- mQsPanel.switchSecurityFooter(true)
-
- mTestableLooper.runWithLooper {
- mQsPanel.isExpanded = true
- }
-
- // After mFooter
- assertThat(mQsPanel.indexOfChild(mQsPanel.mSecurityFooter)).isEqualTo(
- mQsPanel.indexOfChild(mFooter) + 1
- )
- }
-
- @Test
- fun testSecurityFooter_appearsOnBottomIfPortrait() {
- mQsPanel.onConfigurationChanged(getNewOrientationConfig(ORIENTATION_PORTRAIT))
- mQsPanel.switchSecurityFooter(false)
-
- mTestableLooper.runWithLooper {
- mQsPanel.isExpanded = true
- }
-
- // After mFooter
- assertThat(mQsPanel.indexOfChild(mQsPanel.mSecurityFooter)).isEqualTo(
- mQsPanel.indexOfChild(mFooter) + 1
- )
- }
-
- @Test
- fun testSecurityFooter_appearsOnTopIfSmallScreenAndLandscape() {
- mQsPanel.onConfigurationChanged(getNewOrientationConfig(ORIENTATION_LANDSCAPE))
- mQsPanel.switchSecurityFooter(false)
-
- mTestableLooper.runWithLooper {
- mQsPanel.isExpanded = true
- }
-
- // -1 means that it is part of the mHeaderContainer
- assertThat(mQsPanel.indexOfChild(mQsPanel.mSecurityFooter)).isEqualTo(-1)
- }
-
- @Test
fun testHasCollapseAccessibilityAction() {
val info = AccessibilityNodeInfo(mQsPanel)
mQsPanel.onInitializeAccessibilityNodeInfo(info)
@@ -128,7 +80,4 @@ class QSPanelTest : SysuiTestCase() {
mQsPanel.performAccessibilityAction(AccessibilityNodeInfo.ACTION_COLLAPSE, null)
verify(mockRunnable).run()
}
-
- private fun getNewOrientationConfig(@Configuration.Orientation newOrientation: Int) =
- context.resources.configuration.apply { orientation = newOrientation }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt
index 60c2bde947a6..a6a584d2e622 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt
@@ -2,11 +2,9 @@ package com.android.systemui.qs
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
-import android.view.View
import android.view.ViewGroup
import android.view.accessibility.AccessibilityNodeInfo
import android.widget.FrameLayout
-import android.widget.LinearLayout
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth
@@ -37,8 +35,6 @@ class QuickQSPanelTest : SysuiTestCase() {
quickQSPanel.initialize()
quickQSPanel.onFinishInflate()
- quickQSPanel.setSecurityFooter(View(mContext), false)
- quickQSPanel.setHeaderContainer(LinearLayout(mContext))
// Provides a parent with non-zero size for QSPanel
parentView = FrameLayout(mContext).apply {
addView(quickQSPanel)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
index 1bf83513d472..3d9205ee0354 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
@@ -21,6 +21,7 @@ import android.os.Handler
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.internal.logging.MetricsLogger
import com.android.systemui.SysuiTestCase
@@ -38,6 +39,9 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.`when`
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@@ -63,6 +67,8 @@ class BatterySaverTileTest : SysuiTestCase() {
private lateinit var qsLogger: QSLogger
@Mock
private lateinit var batteryController: BatteryController
+ @Mock
+ private lateinit var view: View
private lateinit var secureSettings: SecureSettings
private lateinit var testableLooper: TestableLooper
private lateinit var tile: BatterySaverTile
@@ -105,4 +111,26 @@ class BatterySaverTileTest : SysuiTestCase() {
assertEquals(USER + 1, tile.mSetting.currentUser)
}
+
+ @Test
+ fun testClickingPowerSavePassesView() {
+ tile.onPowerSaveChanged(true)
+ tile.handleClick(view)
+
+ tile.onPowerSaveChanged(false)
+ tile.handleClick(view)
+
+ verify(batteryController).setPowerSaveMode(true, view)
+ verify(batteryController).setPowerSaveMode(false, view)
+ }
+
+ @Test
+ fun testStopListeningClearsViewInController() {
+ clearInvocations(batteryController)
+ tile.handleSetListening(true)
+ verify(batteryController, never()).clearLastPowerSaverStartView()
+
+ tile.handleSetListening(false)
+ verify(batteryController).clearLastPowerSaverStartView()
+ }
} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
index 9a4e10cec159..497a857d1deb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
@@ -22,8 +22,6 @@ import android.view.View.VISIBLE
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.StatusBarState.KEYGUARD
-import com.android.systemui.statusbar.StatusBarState.SHADE
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
@@ -142,11 +140,13 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
}
@Test
- fun computeHeight_returnsLessThanAvailableSpaceUsedToCalculateMaxNotifications() {
+ fun computeHeight_returnsAtMostSpaceAvailable_withGapBeforeShelf() {
val rowHeight = ROW_HEIGHT
val shelfHeight = SHELF_HEIGHT
val totalSpaceForEachRow = GAP_HEIGHT + rowHeight + NOTIFICATION_PADDING
val availableSpace = totalSpaceForEachRow * 2
+
+ // All rows in separate sections (default setup).
val rows =
listOf(
createMockRow(rowHeight),
@@ -157,6 +157,28 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
assertThat(maxNotifications).isEqualTo(2)
val height = sizeCalculator.computeHeight(stackLayout, maxNotifications, SHELF_HEIGHT)
+ assertThat(height).isAtMost(availableSpace + GAP_HEIGHT + SHELF_HEIGHT)
+ }
+
+ @Test
+ fun computeHeight_returnsAtMostSpaceAvailable_noGapBeforeShelf() {
+ val rowHeight = ROW_HEIGHT
+ val shelfHeight = SHELF_HEIGHT
+ val totalSpaceForEachRow = GAP_HEIGHT + rowHeight + NOTIFICATION_PADDING
+ val availableSpace = totalSpaceForEachRow * 1
+
+ // Both rows are in the same section.
+ whenever(stackLayout.calculateGapHeight(nullable(), nullable(), any()))
+ .thenReturn(0f)
+ val rows =
+ listOf(
+ createMockRow(rowHeight),
+ createMockRow(rowHeight))
+
+ val maxNotifications = computeMaxKeyguardNotifications(rows, availableSpace, shelfHeight)
+ assertThat(maxNotifications).isEqualTo(1)
+
+ val height = sizeCalculator.computeHeight(stackLayout, maxNotifications, SHELF_HEIGHT)
assertThat(height).isAtMost(availableSpace + SHELF_HEIGHT)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt
index 05a21db310fc..e2fabbd1c270 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt
@@ -13,7 +13,6 @@ import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.navigationbar.NavigationModeController
import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener
import com.android.systemui.recents.OverviewProxyService
@@ -114,25 +113,6 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
@Test
fun testTaskbarVisibleInSplitShade() {
enableSplitShade()
- useNewFooter(false)
-
- given(taskbarVisible = true,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0, // taskbar should disappear when shade is expanded
- expectedNotificationsMargin = NOTIFICATIONS_MARGIN)
-
- given(taskbarVisible = true,
- navigationMode = BUTTONS_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = STABLE_INSET_BOTTOM,
- expectedNotificationsMargin = NOTIFICATIONS_MARGIN)
- }
-
- @Test
- fun testTaskbarVisibleInSplitShade_newFooter() {
- enableSplitShade()
- useNewFooter(true)
given(taskbarVisible = true,
navigationMode = GESTURES_NAVIGATION,
@@ -153,25 +133,6 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
fun testTaskbarNotVisibleInSplitShade() {
// when taskbar is not visible, it means we're on the home screen
enableSplitShade()
- useNewFooter(false)
-
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0)
-
- given(taskbarVisible = false,
- navigationMode = BUTTONS_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons
- expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN)
- }
-
- @Test
- fun testTaskbarNotVisibleInSplitShade_newFooter() {
- // when taskbar is not visible, it means we're on the home screen
- enableSplitShade()
- useNewFooter(true)
given(taskbarVisible = false,
navigationMode = GESTURES_NAVIGATION,
@@ -190,24 +151,6 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
@Test
fun testTaskbarNotVisibleInSplitShadeWithCutout() {
enableSplitShade()
- useNewFooter(false)
-
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withCutout())
- then(expectedContainerPadding = CUTOUT_HEIGHT)
-
- given(taskbarVisible = false,
- navigationMode = BUTTONS_NAVIGATION,
- insets = windowInsets().withCutout().withStableBottom())
- then(expectedContainerPadding = 0,
- expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN)
- }
-
- @Test
- fun testTaskbarNotVisibleInSplitShadeWithCutout_newFooter() {
- enableSplitShade()
- useNewFooter(true)
given(taskbarVisible = false,
navigationMode = GESTURES_NAVIGATION,
@@ -226,23 +169,6 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
@Test
fun testTaskbarVisibleInSinglePaneShade() {
disableSplitShade()
- useNewFooter(false)
-
- given(taskbarVisible = true,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0)
-
- given(taskbarVisible = true,
- navigationMode = BUTTONS_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = STABLE_INSET_BOTTOM)
- }
-
- @Test
- fun testTaskbarVisibleInSinglePaneShade_newFooter() {
- disableSplitShade()
- useNewFooter(true)
given(taskbarVisible = true,
navigationMode = GESTURES_NAVIGATION,
@@ -260,28 +186,6 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
@Test
fun testTaskbarNotVisibleInSinglePaneShade() {
disableSplitShade()
- useNewFooter(false)
-
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = emptyInsets())
- then(expectedContainerPadding = 0)
-
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withCutout().withStableBottom())
- then(expectedContainerPadding = CUTOUT_HEIGHT)
-
- given(taskbarVisible = false,
- navigationMode = BUTTONS_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0, expectedQsPadding = STABLE_INSET_BOTTOM)
- }
-
- @Test
- fun testTaskbarNotVisibleInSinglePaneShade_newFooter() {
- disableSplitShade()
- useNewFooter(true)
given(taskbarVisible = false,
navigationMode = GESTURES_NAVIGATION,
@@ -303,27 +207,6 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
fun testCustomizingInSinglePaneShade() {
disableSplitShade()
controller.setCustomizerShowing(true)
- useNewFooter(false)
-
- // always sets spacings to 0
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0,
- expectedNotificationsMargin = 0)
-
- given(taskbarVisible = false,
- navigationMode = BUTTONS_NAVIGATION,
- insets = emptyInsets())
- then(expectedContainerPadding = 0,
- expectedNotificationsMargin = 0)
- }
-
- @Test
- fun testCustomizingInSinglePaneShade_newFooter() {
- disableSplitShade()
- controller.setCustomizerShowing(true)
- useNewFooter(true)
// always sets spacings to 0
given(taskbarVisible = false,
@@ -343,27 +226,6 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
fun testDetailShowingInSinglePaneShade() {
disableSplitShade()
controller.setDetailShowing(true)
- useNewFooter(false)
-
- // always sets spacings to 0
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0,
- expectedNotificationsMargin = 0)
-
- given(taskbarVisible = false,
- navigationMode = BUTTONS_NAVIGATION,
- insets = emptyInsets())
- then(expectedContainerPadding = 0,
- expectedNotificationsMargin = 0)
- }
-
- @Test
- fun testDetailShowingInSinglePaneShade_newFooter() {
- disableSplitShade()
- controller.setDetailShowing(true)
- useNewFooter(true)
// always sets spacings to 0
given(taskbarVisible = false,
@@ -383,25 +245,6 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
fun testDetailShowingInSplitShade() {
enableSplitShade()
controller.setDetailShowing(true)
- useNewFooter(false)
-
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0)
-
- // should not influence spacing
- given(taskbarVisible = false,
- navigationMode = BUTTONS_NAVIGATION,
- insets = emptyInsets())
- then(expectedContainerPadding = 0)
- }
-
- @Test
- fun testDetailShowingInSplitShade_newFooter() {
- enableSplitShade()
- controller.setDetailShowing(true)
- useNewFooter(true)
given(taskbarVisible = false,
navigationMode = GESTURES_NAVIGATION,
@@ -531,7 +374,6 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
@Test
fun testWindowInsetDebounce() {
disableSplitShade()
- useNewFooter(true)
given(taskbarVisible = false,
navigationMode = GESTURES_NAVIGATION,
@@ -596,13 +438,8 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
verify(notificationsQSContainer)
.setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding))
verify(notificationsQSContainer).setNotificationsMarginBottom(expectedNotificationsMargin)
- val newFooter = featureFlags.isEnabled(Flags.NEW_FOOTER)
- if (newFooter) {
- verify(notificationsQSContainer)
+ verify(notificationsQSContainer)
.setQSContainerPaddingBottom(expectedQsPadding)
- } else {
- verify(notificationsQSContainer).setQSScrollPaddingBottom(expectedQsPadding)
- }
Mockito.clearInvocations(notificationsQSContainer)
}
@@ -620,10 +457,6 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
return this
}
- private fun useNewFooter(useNewFooter: Boolean) {
- whenever(featureFlags.isEnabled(Flags.NEW_FOOTER)).thenReturn(useNewFooter)
- }
-
private fun getConstraintSetLayout(@IdRes id: Int): ConstraintSet.Layout {
return constraintSetCaptor.value.getConstraint(id).layout
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 509509401d13..497f7fba2dbf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -59,6 +59,7 @@ import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentCom
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.CarrierConfigTracker;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.time.FakeSystemClock;
@@ -90,6 +91,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
private OperatorNameViewController mOperatorNameViewController;
private SecureSettings mSecureSettings;
private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+ private final CarrierConfigTracker mCarrierConfigTracker = mock(CarrierConfigTracker.class);
@Mock
private StatusBarFragmentComponent.Factory mStatusBarFragmentComponentFactory;
@@ -373,6 +375,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
mNetworkController,
mStatusBarStateController,
mCommandQueue,
+ mCarrierConfigTracker,
new CollapsedStatusBarFragmentLogger(
new LogBuffer("TEST", 1, 1, mock(LogcatEchoTracker.class)),
new DisableFlagsLogger()
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 2577dbdbb593..b714df50106e 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
@@ -18,6 +18,10 @@ package com.android.systemui.statusbar.policy;
import static android.os.BatteryManager.EXTRA_PRESENT;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker;
+
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -30,20 +34,24 @@ import android.os.PowerSaveState;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.View;
+import com.android.dx.mockito.inline.extended.StaticInOrder;
+import com.android.settingslib.fuelgauge.BatterySaverUtils;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.power.EnhancedEstimates;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
+import org.junit.After;
import org.junit.Assert;
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)
@@ -53,11 +61,19 @@ public class BatteryControllerTest extends SysuiTestCase {
@Mock private PowerManager mPowerManager;
@Mock private BroadcastDispatcher mBroadcastDispatcher;
@Mock private DemoModeController mDemoModeController;
+ @Mock private View mView;
private BatteryControllerImpl mBatteryController;
+ private MockitoSession mMockitoSession;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ mMockitoSession = mockitoSession()
+ .initMocks(this)
+ .mockStatic(BatterySaverUtils.class)
+ .startMocking();
+
mBatteryController = new BatteryControllerImpl(getContext(),
mock(EnhancedEstimates.class),
mPowerManager,
@@ -68,6 +84,11 @@ public class BatteryControllerTest extends SysuiTestCase {
mBatteryController.init();
}
+ @After
+ public void tearDown() {
+ mMockitoSession.finishMocking();
+ }
+
@Test
public void testBatteryInitialized() {
Assert.assertTrue(mBatteryController.mHasReceivedBattery);
@@ -135,4 +156,33 @@ public class BatteryControllerTest extends SysuiTestCase {
// THEN it is informed about the battery state
verify(cb, atLeastOnce()).onBatteryUnknownStateChanged(true);
}
+
+ @Test
+ public void testBatteryUtilsCalledOnSetPowerSaveMode() {
+ mBatteryController.setPowerSaveMode(true, mView);
+ mBatteryController.setPowerSaveMode(false, mView);
+
+ StaticInOrder inOrder = inOrder(staticMockMarker(BatterySaverUtils.class));
+ inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), true, true));
+ inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), false, true));
+ }
+
+ @Test
+ public void testSaveViewReferenceWhenSettingPowerSaveMode() {
+ mBatteryController.setPowerSaveMode(false, mView);
+
+ Assert.assertNull(mBatteryController.getLastPowerSaverStartView());
+
+ mBatteryController.setPowerSaveMode(true, mView);
+
+ Assert.assertSame(mView, mBatteryController.getLastPowerSaverStartView().get());
+ }
+
+ @Test
+ public void testClearViewReference() {
+ mBatteryController.setPowerSaveMode(true, mView);
+ mBatteryController.clearLastPowerSaverStartView();
+
+ Assert.assertNull(mBatteryController.getLastPowerSaverStartView());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java
index 50c1e73f6aac..9ca4db4c1843 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java
@@ -16,6 +16,7 @@ package com.android.systemui.utils.leaks;
import android.os.Bundle;
import android.testing.LeakCheck;
+import android.view.View;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
@@ -47,6 +48,11 @@ public class FakeBatteryController extends BaseLeakChecker<BatteryStateChangeCal
}
@Override
+ public void setPowerSaveMode(boolean powerSave, View view) {
+
+ }
+
+ @Override
public boolean isPluggedIn() {
return false;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
index 40657fb61412..ce7924a2a4a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
@@ -807,7 +807,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
assertTrue(mBubbleController.hasBubbles());
// Removes the notification
- mEntryListener.onEntryRemoved(mRow, 0);
+ mEntryListener.onEntryRemoved(mRow, REASON_APP_CANCEL);
assertFalse(mBubbleController.hasBubbles());
}
@@ -938,7 +938,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase {
mBubblesManager.handleDismissalInterception(groupSummary.getEntry());
// WHEN the summary is cancelled by the app
- mEntryListener.onEntryRemoved(groupSummary.getEntry(), 0);
+ mEntryListener.onEntryRemoved(groupSummary.getEntry(), REASON_APP_CANCEL);
// THEN the summary and its children are removed from bubble data
assertFalse(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java b/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java
index 7e3ede15aa04..d75d6484bec3 100644
--- a/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java
@@ -34,6 +34,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Queue;
import java.util.Set;
+import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -374,7 +375,8 @@ public class BackupTransportClient {
private <T> T getFutureResult(AndroidFuture<T> future) {
try {
return future.get(600, TimeUnit.SECONDS);
- } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ } catch (InterruptedException | ExecutionException | TimeoutException
+ | CancellationException e) {
Slog.w(TAG, "Failed to get result from transport:", e);
return null;
} finally {
@@ -403,7 +405,11 @@ public class BackupTransportClient {
void cancelActiveFutures() {
synchronized (mActiveFuturesLock) {
for (AndroidFuture<?> future : mActiveFutures) {
- future.cancel(true);
+ try {
+ future.cancel(true);
+ } catch (CancellationException ignored) {
+ // This is expected, so ignore the exception.
+ }
}
mActiveFutures.clear();
}
diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
index 76df8b9f84e8..e78c8d1ddcac 100644
--- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
@@ -24,8 +24,10 @@ import static com.android.server.backup.UserBackupManagerService.BACKUP_METADATA
import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_OPERATION_TIMEOUT;
+import android.annotation.NonNull;
import android.app.ApplicationThreadConstants;
import android.app.IBackupAgent;
+import android.app.backup.BackupAgent;
import android.app.backup.BackupManager;
import android.app.backup.FullBackup;
import android.app.backup.IBackupManagerMonitor;
@@ -38,10 +40,12 @@ import android.content.pm.Signature;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.provider.Settings;
+import android.system.OsConstants;
import android.text.TextUtils;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import com.android.server.backup.BackupAgentTimeoutParameters;
import com.android.server.backup.BackupRestoreTask;
@@ -57,6 +61,7 @@ import com.android.server.backup.utils.FullBackupRestoreObserverUtils;
import com.android.server.backup.utils.RestoreUtils;
import com.android.server.backup.utils.TarBackupReader;
+import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -135,6 +140,8 @@ public class FullRestoreEngine extends RestoreEngine {
private boolean mPipesClosed;
private final BackupEligibilityRules mBackupEligibilityRules;
+ private FileMetadata mReadOnlyParent = null;
+
public FullRestoreEngine(
UserBackupManagerService backupManagerService, OperationStorage operationStorage,
BackupRestoreTask monitorTask, IFullBackupRestoreObserver observer,
@@ -158,6 +165,22 @@ public class FullRestoreEngine extends RestoreEngine {
mBackupEligibilityRules = backupEligibilityRules;
}
+ @VisibleForTesting
+ FullRestoreEngine() {
+ mIsAdbRestore = false;
+ mAllowApks = false;
+ mEphemeralOpToken = 0;
+ mUserId = 0;
+ mBackupEligibilityRules = null;
+ mAgentTimeoutParameters = null;
+ mBuffer = null;
+ mBackupManagerService = null;
+ mOperationStorage = null;
+ mMonitor = null;
+ mMonitorTask = null;
+ mOnlyPackage = null;
+ }
+
public IBackupAgent getAgent() {
return mAgent;
}
@@ -397,6 +420,11 @@ public class FullRestoreEngine extends RestoreEngine {
okay = false;
}
+ if (shouldSkipReadOnlyDir(info)) {
+ // b/194894879: We don't support restore of read-only dirs.
+ okay = false;
+ }
+
// At this point we have an agent ready to handle the full
// restore data as well as a pipe for sending data to
// that agent. Tell the agent to start reading from the
@@ -573,6 +601,45 @@ public class FullRestoreEngine extends RestoreEngine {
return (info != null);
}
+ boolean shouldSkipReadOnlyDir(FileMetadata info) {
+ if (isValidParent(mReadOnlyParent, info)) {
+ // This file has a read-only parent directory, we shouldn't
+ // restore it.
+ return true;
+ } else {
+ // We're now in a different branch of the file tree, update the parent
+ // value.
+ if (isReadOnlyDir(info)) {
+ // Current directory is read-only. Remember it so that we can skip all
+ // of its contents.
+ mReadOnlyParent = info;
+ Slog.w(TAG, "Skipping restore of " + info.path + " and its contents as "
+ + "read-only dirs are currently not supported.");
+ return true;
+ } else {
+ mReadOnlyParent = null;
+ }
+ }
+
+ return false;
+ }
+
+ private static boolean isValidParent(FileMetadata parentDir, @NonNull FileMetadata childDir) {
+ return parentDir != null
+ && childDir.packageName.equals(parentDir.packageName)
+ && childDir.domain.equals(parentDir.domain)
+ && childDir.path.startsWith(getPathWithTrailingSeparator(parentDir.path));
+ }
+
+ private static String getPathWithTrailingSeparator(String path) {
+ return path.endsWith(File.separator) ? path : path + File.separator;
+ }
+
+ private static boolean isReadOnlyDir(FileMetadata file) {
+ // Check if owner has 'write' bit in the file's mode value (see 'man -7 inode' for details).
+ return file.type == BackupAgent.TYPE_DIRECTORY && (file.mode & OsConstants.S_IWUSR) == 0;
+ }
+
private void setUpPipes() throws IOException {
synchronized (mPipesLock) {
mPipes = ParcelFileDescriptor.createPipe();
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 5406f711b73d..89c8ca567dd9 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -120,7 +120,6 @@ java_library_static {
"java/com/android/server/am/EventLogTags.logtags",
"java/com/android/server/wm/EventLogTags.logtags",
"java/com/android/server/policy/EventLogTags.logtags",
- ":services.connectivity-tiramisu-sources",
],
libs: [
@@ -174,9 +173,6 @@ java_library_static {
"overlayable_policy_aidl-java",
"SurfaceFlingerProperties",
"com.android.sysprop.watchdog",
- // This is used for services.connectivity-tiramisu-sources.
- // TODO: delete when NetworkStatsService is moved to the mainline module.
- "net-utils-device-common-bpf",
],
javac_shard_size: 50,
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index e6bf109ff84c..7ab3008585ce 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -2132,15 +2132,19 @@ class StorageManagerService extends IStorageManager.Stub
}
}
- PackageMonitor monitor = new PackageMonitor() {
+ if (mPackageMonitorsForUser.get(userId) == null) {
+ PackageMonitor monitor = new PackageMonitor() {
@Override
public void onPackageRemoved(String packageName, int uid) {
updateLegacyStorageApps(packageName, uid, false);
}
};
- // TODO(b/149391976): Use different handler?
- monitor.register(mContext, user, true, mHandler);
- mPackageMonitorsForUser.put(userId, monitor);
+ // TODO(b/149391976): Use different handler?
+ monitor.register(mContext, user, true, mHandler);
+ mPackageMonitorsForUser.put(userId, monitor);
+ } else {
+ Slog.w(TAG, "PackageMonitor is already registered for: " + userId);
+ }
}
private static long getLastAccessTime(AppOpsManager manager,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 96761ca82c5f..0da14bc2a96a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -310,7 +310,6 @@ import android.sysprop.InitProperties;
import android.sysprop.VoldProperties;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
-import android.text.format.DateUtils;
import android.text.style.SuggestionSpan;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -8685,7 +8684,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
- private final ArrayMap<String, long[]> mErrorClusterRecords = new ArrayMap<>();
+ private final DropboxRateLimiter mDropboxRateLimiter = new DropboxRateLimiter();
/**
* Write a description of an error (crash, WTF, ANR) to the drop box.
@@ -8720,22 +8719,8 @@ public class ActivityManagerService extends IActivityManager.Stub
final String dropboxTag = processClass(process) + "_" + eventType;
if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return;
- // Rate-limit how often we're willing to do the heavy lifting below to
- // collect and record logs; currently 5 logs per 10 second period per eventType.
- final long now = SystemClock.elapsedRealtime();
- synchronized (mErrorClusterRecords) {
- long[] errRecord = mErrorClusterRecords.get(eventType);
- if (errRecord == null) {
- errRecord = new long[2]; // [0]: startTime, [1]: count
- mErrorClusterRecords.put(eventType, errRecord);
- }
- if (now - errRecord[0] > 10 * DateUtils.SECOND_IN_MILLIS) {
- errRecord[0] = now;
- errRecord[1] = 1L;
- } else {
- if (errRecord[1]++ >= 5) return;
- }
- }
+ // Check if we should rate limit and abort early if needed.
+ if (mDropboxRateLimiter.shouldRateLimit(eventType, processName)) return;
final StringBuilder sb = new StringBuilder(1024);
appendDropBoxProcessHeaders(process, processName, sb);
@@ -17215,17 +17200,17 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
- public boolean isUidCurrentlyInstrumented(int uid) {
+ public int getInstrumentationSourceUid(int uid) {
synchronized (mProcLock) {
for (int i = mActiveInstrumentation.size() - 1; i >= 0; i--) {
ActiveInstrumentation activeInst = mActiveInstrumentation.get(i);
if (!activeInst.mFinished && activeInst.mTargetInfo != null
&& activeInst.mTargetInfo.uid == uid) {
- return true;
+ return activeInst.mSourceUid;
}
}
}
- return false;
+ return INVALID_UID;
}
@Override
diff --git a/services/core/java/com/android/server/am/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java
index 032b129c4256..64ff532b026a 100644
--- a/services/core/java/com/android/server/am/AppBatteryTracker.java
+++ b/services/core/java/com/android/server/am/AppBatteryTracker.java
@@ -49,6 +49,7 @@ import android.content.Context;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.os.AppBackgroundRestrictionsInfo;
import android.os.AppBatteryStatsProto;
import android.os.BatteryConsumer;
import android.os.BatteryConsumer.Dimensions;
@@ -74,6 +75,7 @@ import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.server.am.AppBatteryTracker.AppBatteryPolicy;
import com.android.server.am.AppRestrictionController.TrackerType;
import com.android.server.am.AppRestrictionController.UidBatteryUsageProvider;
@@ -175,6 +177,12 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy>
@GuardedBy("mLock")
private long mLastUidBatteryUsageStartTs;
+ /**
+ * elapseRealTime of last time the AppBatteryTracker is reported to statsd.
+ */
+ @GuardedBy("mLock")
+ private long mLastReportTime = 0;
+
// For debug only.
private final SparseArray<ImmutableBatteryUsage> mDebugUidPercentages = new SparseArray<>();
@@ -228,9 +236,92 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy>
mBgHandler.postDelayed(mBgBatteryUsageStatsPolling, delay);
}
}
+ logAppBatteryTrackerIfNeeded();
}
}
+ /**
+ * Log per-uid BatteryTrackerInfo to statsd every 24 hours (as the window specified in
+ * {@link AppBatteryPolicy#mBgCurrentDrainWindowMs})
+ */
+ private void logAppBatteryTrackerIfNeeded() {
+ final long now = SystemClock.elapsedRealtime();
+ synchronized (mLock) {
+ final AppBatteryPolicy bgPolicy = mInjector.getPolicy();
+ if (now - mLastReportTime < bgPolicy.mBgCurrentDrainWindowMs) {
+ return;
+ } else {
+ mLastReportTime = now;
+ }
+ }
+ updateBatteryUsageStatsIfNecessary(mInjector.currentTimeMillis(), true);
+ synchronized (mLock) {
+ for (int i = 0, size = mUidBatteryUsageInWindow.size(); i < size; i++) {
+ final int uid = mUidBatteryUsageInWindow.keyAt(i);
+ if (!UserHandle.isCore(uid) && !UserHandle.isApp(uid)) {
+ continue;
+ }
+ if (BATTERY_USAGE_NONE.equals(mUidBatteryUsageInWindow.valueAt(i))) {
+ continue;
+ }
+ FrameworkStatsLog.write(FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO,
+ uid,
+ FrameworkStatsLog
+ .APP_BACKGROUND_RESTRICTIONS_INFO__RESTRICTION_LEVEL__LEVEL_UNKNOWN,
+ FrameworkStatsLog
+ .APP_BACKGROUND_RESTRICTIONS_INFO__THRESHOLD__THRESHOLD_UNKNOWN,
+ FrameworkStatsLog
+ .APP_BACKGROUND_RESTRICTIONS_INFO__TRACKER__UNKNOWN_TRACKER,
+ null /*byte[] fgs_tracker_info*/,
+ getBatteryTrackerInfoProtoLocked(uid) /*byte[] battery_tracker_info*/,
+ null /*byte[] broadcast_events_tracker_info*/,
+ null /*byte[] bind_service_events_tracker_info*/,
+ FrameworkStatsLog
+ .APP_BACKGROUND_RESTRICTIONS_INFO__EXEMPTION_REASON__REASON_UNKNOWN,
+ FrameworkStatsLog
+ .APP_BACKGROUND_RESTRICTIONS_INFO__OPT_LEVEL__UNKNOWN,
+ FrameworkStatsLog
+ .APP_BACKGROUND_RESTRICTIONS_INFO__TARGET_SDK__SDK_UNKNOWN,
+ isLowRamDeviceStatic());
+ }
+ }
+ }
+
+ /**
+ * Get the BatteryTrackerInfo proto of a UID.
+ * @param uid
+ * @return byte array of the proto.
+ */
+ @NonNull byte[] getBatteryTrackerInfoProtoLocked(int uid) {
+ final ImmutableBatteryUsage temp = mUidBatteryUsageInWindow.get(uid);
+ if (temp == null) {
+ return new byte[0];
+ }
+ final BatteryUsage bgUsage = temp.calcPercentage(uid, mInjector.getPolicy());
+ final double allUsage = bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_UNSPECIFIED]
+ + bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_FOREGROUND]
+ + bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_BACKGROUND]
+ + bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_FOREGROUND_SERVICE]
+ + bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_CACHED];
+ final double usageBackground =
+ bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_BACKGROUND];
+ final double usageFgs =
+ bgUsage.mPercentage[BatteryUsage.BATTERY_USAGE_INDEX_FOREGROUND_SERVICE];
+ Slog.d(TAG, "getBatteryTrackerInfoProtoLocked uid:" + uid
+ + " allUsage:" + String.format("%4.2f%%", allUsage)
+ + " usageBackground:" + String.format("%4.2f%%", usageBackground)
+ + " usageFgs:" + String.format("%4.2f%%", usageFgs));
+ final ProtoOutputStream proto = new ProtoOutputStream();
+ proto.write(AppBackgroundRestrictionsInfo.BatteryTrackerInfo.BATTERY_24H,
+ allUsage * 10000);
+ proto.write(AppBackgroundRestrictionsInfo.BatteryTrackerInfo.BATTERY_USAGE_BACKGROUND,
+ usageBackground * 10000);
+ proto.write(AppBackgroundRestrictionsInfo.BatteryTrackerInfo.BATTERY_USAGE_FGS,
+ usageFgs * 10000);
+ proto.flush();
+ return proto.getBytes();
+ }
+
@Override
void onUserStarted(final @UserIdInt int userId) {
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/am/AppFGSTracker.java b/services/core/java/com/android/server/am/AppFGSTracker.java
index f8378c3a8a4e..ddd27647c970 100644
--- a/services/core/java/com/android/server/am/AppFGSTracker.java
+++ b/services/core/java/com/android/server/am/AppFGSTracker.java
@@ -52,6 +52,7 @@ import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.SomeArgs;
import com.android.server.am.AppFGSTracker.AppFGSPolicy;
import com.android.server.am.AppFGSTracker.PackageDurations;
import com.android.server.am.AppRestrictionController.TrackerType;
@@ -112,9 +113,14 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac
@Override
public void onForegroundServiceNotificationUpdated(String packageName, int uid,
- int foregroundId) {
- mHandler.obtainMessage(MyHandler.MSG_FOREGROUND_SERVICES_NOTIFICATION_UPDATED,
- uid, foregroundId, packageName).sendToTarget();
+ int foregroundId, boolean canceling) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.argi1 = uid;
+ args.argi2 = foregroundId;
+ args.arg1 = packageName;
+ args.arg2 = canceling ? Boolean.TRUE : Boolean.FALSE;
+ mHandler.obtainMessage(MyHandler.MSG_FOREGROUND_SERVICES_NOTIFICATION_UPDATED, args)
+ .sendToTarget();
}
private static class MyHandler extends Handler {
@@ -149,8 +155,10 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac
(String) msg.obj, msg.arg1, msg.arg2);
break;
case MSG_FOREGROUND_SERVICES_NOTIFICATION_UPDATED:
+ final SomeArgs args = (SomeArgs) msg.obj;
mTracker.handleForegroundServiceNotificationUpdated(
- (String) msg.obj, msg.arg1, msg.arg2);
+ (String) args.arg1, args.argi1, args.argi2, (Boolean) args.arg2);
+ args.recycle();
break;
case MSG_CHECK_LONG_RUNNING_FGS:
mTracker.checkLongRunningFgs();
@@ -241,18 +249,18 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac
}
private void handleForegroundServiceNotificationUpdated(String packageName, int uid,
- int notificationId) {
+ int notificationId, boolean canceling) {
synchronized (mLock) {
SparseBooleanArray notificationIDs = mFGSNotificationIDs.get(uid, packageName);
- if (notificationId > 0) {
+ if (!canceling) {
if (notificationIDs == null) {
notificationIDs = new SparseBooleanArray();
mFGSNotificationIDs.put(uid, packageName, notificationIDs);
}
notificationIDs.put(notificationId, false);
- } else if (notificationId < 0) {
+ } else {
if (notificationIDs != null) {
- final int indexOfKey = notificationIDs.indexOfKey(-notificationId);
+ final int indexOfKey = notificationIDs.indexOfKey(notificationId);
if (indexOfKey >= 0) {
final boolean wasVisible = notificationIDs.valueAt(indexOfKey);
notificationIDs.removeAt(indexOfKey);
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index 708bd1662a77..6f7435943d45 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -1975,6 +1975,9 @@ public final class AppRestrictionController {
}
try {
final PackageInfo pkg = pm.getPackageInfo(packageName, 0 /* flags */);
+ if (pkg == null || pkg.applicationInfo == null) {
+ return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__TARGET_SDK__SDK_UNKNOWN;
+ }
final int targetSdk = pkg.applicationInfo.targetSdkVersion;
if (targetSdk < Build.VERSION_CODES.S) {
return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__TARGET_SDK__SDK_PRE_S;
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index c70fe037e393..ade44eac4583 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -880,9 +880,9 @@ public final class BroadcastQueue {
// Ensure that broadcasts are only sent to other apps if they are explicitly marked as
// exported, or are System level broadcasts
- if (!skip && !filter.exported && mService.checkComponentPermission(null, r.callingPid,
- r.callingUid, filter.receiverList.uid, filter.exported)
- != PackageManager.PERMISSION_GRANTED) {
+ if (!skip && !filter.exported && !Process.isCoreUid(r.callingUid)
+ && filter.receiverList.uid != r.callingUid) {
+
Slog.w(TAG, "Exported Denial: sending "
+ r.intent.toString()
+ ", action: " + r.intent.getAction()
diff --git a/services/core/java/com/android/server/am/DropboxRateLimiter.java b/services/core/java/com/android/server/am/DropboxRateLimiter.java
new file mode 100644
index 000000000000..c51702359a6b
--- /dev/null
+++ b/services/core/java/com/android/server/am/DropboxRateLimiter.java
@@ -0,0 +1,125 @@
+/*
+ * 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.server.am;
+
+import android.os.SystemClock;
+import android.text.format.DateUtils;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.GuardedBy;
+
+/** Rate limiter for adding errors into dropbox. */
+public class DropboxRateLimiter {
+ private static final long RATE_LIMIT_BUFFER_EXPIRY = 15 * DateUtils.SECOND_IN_MILLIS;
+ private static final long RATE_LIMIT_BUFFER_DURATION = 10 * DateUtils.SECOND_IN_MILLIS;
+ private static final int RATE_LIMIT_ALLOWED_ENTRIES = 5;
+
+ @GuardedBy("mErrorClusterRecords")
+ private final ArrayMap<String, ErrorRecord> mErrorClusterRecords = new ArrayMap<>();
+ private final Clock mClock;
+
+ private long mLastMapCleanUp = 0L;
+
+ public DropboxRateLimiter() {
+ this(new DefaultClock());
+ }
+
+ public DropboxRateLimiter(Clock clock) {
+ mClock = clock;
+ }
+
+ /** The interface clock to use for tracking the time elapsed. */
+ public interface Clock {
+ /** How long in millis has passed since the device came online. */
+ long uptimeMillis();
+ }
+
+ /** Determines whether dropbox entries of a specific tag and process should be rate limited. */
+ public boolean shouldRateLimit(String eventType, String processName) {
+ // Rate-limit how often we're willing to do the heavy lifting to collect and record logs.
+ final long now = mClock.uptimeMillis();
+ synchronized (mErrorClusterRecords) {
+ // Remove expired records if enough time has passed since the last cleanup.
+ maybeRemoveExpiredRecords(now);
+
+ ErrorRecord errRecord = mErrorClusterRecords.get(errorKey(eventType, processName));
+ if (errRecord == null) {
+ errRecord = new ErrorRecord(now, 1);
+ mErrorClusterRecords.put(errorKey(eventType, processName), errRecord);
+ } else if (now - errRecord.getStartTime() > RATE_LIMIT_BUFFER_DURATION) {
+ errRecord.setStartTime(now);
+ errRecord.setCount(1);
+ } else {
+ errRecord.incrementCount();
+ if (errRecord.getCount() > RATE_LIMIT_ALLOWED_ENTRIES) return true;
+ }
+ }
+ return false;
+ }
+
+ private void maybeRemoveExpiredRecords(long now) {
+ if (now - mLastMapCleanUp <= RATE_LIMIT_BUFFER_EXPIRY) return;
+
+ for (int i = mErrorClusterRecords.size() - 1; i >= 0; i--) {
+ if (now - mErrorClusterRecords.valueAt(i).getStartTime() > RATE_LIMIT_BUFFER_EXPIRY) {
+ mErrorClusterRecords.removeAt(i);
+ }
+ }
+
+ mLastMapCleanUp = now;
+ }
+
+ String errorKey(String eventType, String processName) {
+ return eventType + processName;
+ }
+
+ private class ErrorRecord {
+ long mStartTime;
+ int mCount;
+
+ ErrorRecord(long startTime, int count) {
+ mStartTime = startTime;
+ mCount = count;
+ }
+
+ public void setStartTime(long startTime) {
+ mStartTime = startTime;
+ }
+
+ public void setCount(int count) {
+ mCount = count;
+ }
+
+ public void incrementCount() {
+ mCount++;
+ }
+
+ public long getStartTime() {
+ return mStartTime;
+ }
+
+ public int getCount() {
+ return mCount;
+ }
+ }
+
+ private static class DefaultClock implements Clock {
+ public long uptimeMillis() {
+ return SystemClock.uptimeMillis();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 639f56cb12dd..5a55b8b33cad 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -1112,7 +1112,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
foregroundNoti = localForegroundNoti; // save it for amending next time
signalForegroundServiceNotification(packageName, appInfo.uid,
- localForegroundId);
+ localForegroundId, false /* canceling */);
} catch (RuntimeException e) {
Slog.w(TAG, "Error showing notification for service", e);
@@ -1147,17 +1147,18 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
} catch (RuntimeException e) {
Slog.w(TAG, "Error canceling notification for service", e);
}
- signalForegroundServiceNotification(packageName, appInfo.uid, -localForegroundId);
+ signalForegroundServiceNotification(packageName, appInfo.uid, localForegroundId,
+ true /* canceling */);
}
});
}
private void signalForegroundServiceNotification(String packageName, int uid,
- int foregroundId) {
+ int foregroundId, boolean canceling) {
synchronized (ams) {
for (int i = ams.mForegroundServiceStateListeners.size() - 1; i >= 0; i--) {
ams.mForegroundServiceStateListeners.get(i).onForegroundServiceNotificationUpdated(
- packageName, appInfo.uid, foregroundId);
+ packageName, appInfo.uid, foregroundId, canceling);
}
}
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 361629b0a629..752e17e59a6e 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -2367,7 +2367,8 @@ public class AppOpsService extends IAppOpsService.Stub {
ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
boolean isSelfRequest = (filter & FILTER_BY_UID) != 0 && uid == Binder.getCallingUid();
if (!isSelfRequest) {
- boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(Binder.getCallingUid());
+ boolean isCallerInstrumented =
+ ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID;
boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
boolean isCallerPermissionController;
try {
@@ -6894,7 +6895,8 @@ public class AppOpsService extends IAppOpsService.Stub {
@Override
public @Nullable RuntimeAppOpAccessMessage collectRuntimeAppOpAccessMessage() {
ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
- boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(Binder.getCallingUid());
+ boolean isCallerInstrumented =
+ ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID;
boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
if (!isCallerSystem && !isCallerInstrumented) {
return null;
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index d22a562651dc..04fcda7835fa 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -327,15 +327,29 @@ public class SpatializerHelper {
setDispatchAvailableState(false);
}
- if (able && enabledAvailable.first) {
+ boolean enabled = able && enabledAvailable.first;
+ if (enabled) {
loglogi("Enabling Spatial Audio since enabled for media device:"
+ ROUTING_DEVICES[0]);
} else {
loglogi("Disabling Spatial Audio since disabled for media device:"
+ ROUTING_DEVICES[0]);
}
- setDispatchFeatureEnabledState(able && enabledAvailable.first,
- "onRoutingUpdated");
+ if (mSpat != null) {
+ byte level = enabled ? (byte) Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL
+ : (byte) Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+ loglogi("Setting spatialization level to: " + level);
+ try {
+ mSpat.setLevel(level);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Can't set spatializer level", e);
+ mState = STATE_NOT_SUPPORTED;
+ mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+ enabled = false;
+ }
+ }
+
+ setDispatchFeatureEnabledState(enabled, "onRoutingUpdated");
if (mDesiredHeadTrackingMode != Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED
&& mDesiredHeadTrackingMode != Spatializer.HEAD_TRACKING_MODE_DISABLED) {
@@ -635,7 +649,6 @@ public class SpatializerHelper {
init(true);
}
setSpatializerEnabledInt(true);
- onRoutingUpdated();
} else {
setSpatializerEnabledInt(false);
}
@@ -657,6 +670,7 @@ public class SpatializerHelper {
case STATE_DISABLED_AVAILABLE:
if (enabled) {
createSpat();
+ onRoutingUpdated();
break;
} else {
// already in disabled state
@@ -823,14 +837,13 @@ public class SpatializerHelper {
mSpatHeadTrackingCallback = new SpatializerHeadTrackingCallback();
mSpat = AudioSystem.getSpatializer(mSpatCallback);
try {
- mSpat.setLevel((byte) Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL);
mIsHeadTrackingSupported = mSpat.isHeadTrackingSupported();
//TODO: register heatracking callback only when sensors are registered
if (mIsHeadTrackingSupported) {
mSpat.registerHeadTrackingCallback(mSpatHeadTrackingCallback);
}
} catch (RemoteException e) {
- Log.e(TAG, "Can't set spatializer level", e);
+ Log.e(TAG, "Can't configure head tracking", e);
mState = STATE_NOT_SUPPORTED;
mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 5a105f551ab2..5fcdc8a08b5d 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -70,6 +70,8 @@ import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.DisplayGroupListener;
import android.hardware.display.DisplayManagerInternal.DisplayTransactionListener;
+import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation;
+import android.hardware.display.DisplayManagerInternal.RefreshRateRange;
import android.hardware.display.DisplayViewport;
import android.hardware.display.DisplayedContentSample;
import android.hardware.display.DisplayedContentSamplingAttributes;
@@ -708,9 +710,6 @@ public final class DisplayManagerService extends SystemService {
synchronized (mSyncRoot) {
final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
if (display != null) {
- // Do not let constrain be overwritten by override from WindowManager.
- info.shouldConstrainMetricsForLauncher =
- display.getDisplayInfoLocked().shouldConstrainMetricsForLauncher;
if (display.setDisplayInfoOverrideFromWindowManagerLocked(info)) {
handleLogicalDisplayChangedLocked(display);
}
@@ -2212,21 +2211,6 @@ public final class DisplayManagerService extends SystemService {
}
}
- void setShouldConstrainMetricsForLauncher(boolean constrain) {
- // Apply constrain for every display.
- synchronized (mSyncRoot) {
- int[] displayIds = mLogicalDisplayMapper.getDisplayIdsLocked(Process.myUid());
- for (int i : displayIds) {
- final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(i);
- if (display == null) {
- return;
- }
- display.getDisplayInfoLocked().shouldConstrainMetricsForLauncher = constrain;
- setDisplayInfoOverrideFromWindowManagerInternal(i, display.getDisplayInfoLocked());
- }
- }
- }
-
void setDockedAndIdleEnabled(boolean enabled, int displayId) {
synchronized (mSyncRoot) {
final DisplayPowerController displayPowerController = mDisplayPowerControllers.get(
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index bfdac5781b75..7dce2380407e 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -60,8 +60,6 @@ class DisplayManagerShellCommand extends ShellCommand {
return setDisplayModeDirectorLoggingEnabled(false);
case "dwb-set-cct":
return setAmbientColorTemperatureOverride();
- case "constrain-launcher-metrics":
- return setConstrainLauncherMetrics();
case "set-user-preferred-display-mode":
return setUserPreferredDisplayMode();
case "clear-user-preferred-display-mode":
@@ -112,9 +110,6 @@ class DisplayManagerShellCommand extends ShellCommand {
pw.println(" Disable display mode director logging.");
pw.println(" dwb-set-cct CCT");
pw.println(" Sets the ambient color temperature override to CCT (use -1 to disable).");
- pw.println(" constrain-launcher-metrics [true|false]");
- pw.println(" Sets if Display#getRealSize and getRealMetrics should be constrained for ");
- pw.println(" Launcher.");
pw.println(" set-user-preferred-display-mode WIDTH HEIGHT REFRESH-RATE "
+ "DISPLAY_ID (optional)");
pw.println(" Sets the user preferred display mode which has fields WIDTH, HEIGHT and "
@@ -205,17 +200,6 @@ class DisplayManagerShellCommand extends ShellCommand {
return 0;
}
- private int setConstrainLauncherMetrics() {
- String constrainText = getNextArg();
- if (constrainText == null) {
- getErrPrintWriter().println("Error: no value specified");
- return 1;
- }
- boolean constrain = Boolean.parseBoolean(constrainText);
- mService.setShouldConstrainMetricsForLauncher(constrain);
- return 0;
- }
-
private int setUserPreferredDisplayMode() {
final String widthText = getNextArg();
if (widthText == null) {
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index b7ad4ed4f98d..a640497d7e10 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -233,8 +233,6 @@ final class LogicalDisplay {
info.displayCutout = mOverrideDisplayInfo.displayCutout;
info.logicalDensityDpi = mOverrideDisplayInfo.logicalDensityDpi;
info.roundedCorners = mOverrideDisplayInfo.roundedCorners;
- info.shouldConstrainMetricsForLauncher =
- mOverrideDisplayInfo.shouldConstrainMetricsForLauncher;
}
mInfo.set(info);
}
diff --git a/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java b/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java
new file mode 100644
index 000000000000..d7563e085e61
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java
@@ -0,0 +1,103 @@
+/*
+ * 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.server.hdmi;
+
+/**
+ * Action to query and track the audio status of the System Audio device when enabling or using
+ * Absolute Volume Control. Must be removed when AVC is disabled. Performs two main functions:
+ * 1. When enabling AVC: queries the starting audio status of the System Audio device and
+ * enables the feature upon receiving a response.
+ * 2. While AVC is enabled: monitors <Report Audio Status> messages from the System Audio device and
+ * notifies AudioService if the audio status changes.
+ */
+final class AbsoluteVolumeAudioStatusAction extends HdmiCecFeatureAction {
+ private static final String TAG = "AbsoluteVolumeAudioStatusAction";
+
+ private int mInitialAudioStatusRetriesLeft = 2;
+
+ private static final int STATE_WAIT_FOR_INITIAL_AUDIO_STATUS = 1;
+ private static final int STATE_MONITOR_AUDIO_STATUS = 2;
+
+ private final int mTargetAddress;
+
+ private AudioStatus mLastAudioStatus;
+
+ AbsoluteVolumeAudioStatusAction(HdmiCecLocalDevice source, int targetAddress) {
+ super(source);
+ mTargetAddress = targetAddress;
+ }
+
+ @Override
+ boolean start() {
+ mState = STATE_WAIT_FOR_INITIAL_AUDIO_STATUS;
+ sendGiveAudioStatus();
+ return true;
+ }
+
+ void updateVolume(int volumeIndex) {
+ mLastAudioStatus = new AudioStatus(volumeIndex, mLastAudioStatus.getMute());
+ }
+
+ private void sendGiveAudioStatus() {
+ addTimer(mState, HdmiConfig.TIMEOUT_MS);
+ sendCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(getSourceAddress(), mTargetAddress));
+ }
+
+ @Override
+ boolean processCommand(HdmiCecMessage cmd) {
+ switch (cmd.getOpcode()) {
+ case Constants.MESSAGE_REPORT_AUDIO_STATUS:
+ return handleReportAudioStatus(cmd);
+ }
+
+ return false;
+ }
+
+ private boolean handleReportAudioStatus(HdmiCecMessage cmd) {
+ if (mTargetAddress != cmd.getSource() || cmd.getParams().length == 0) {
+ return false;
+ }
+
+ boolean mute = HdmiUtils.isAudioStatusMute(cmd);
+ int volume = HdmiUtils.getAudioStatusVolume(cmd);
+ AudioStatus audioStatus = new AudioStatus(volume, mute);
+ if (mState == STATE_WAIT_FOR_INITIAL_AUDIO_STATUS) {
+ localDevice().getService().enableAbsoluteVolumeControl(audioStatus);
+ mState = STATE_MONITOR_AUDIO_STATUS;
+ } else if (mState == STATE_MONITOR_AUDIO_STATUS) {
+ if (audioStatus.getVolume() != mLastAudioStatus.getVolume()) {
+ localDevice().getService().notifyAvcVolumeChange(audioStatus.getVolume());
+ }
+ if (audioStatus.getMute() != mLastAudioStatus.getMute()) {
+ localDevice().getService().notifyAvcMuteChange(audioStatus.getMute());
+ }
+ }
+ mLastAudioStatus = audioStatus;
+
+ return true;
+ }
+
+ @Override
+ void handleTimerEvent(int state) {
+ if (mState != state) {
+ return;
+ } else if (mInitialAudioStatusRetriesLeft > 0) {
+ mInitialAudioStatusRetriesLeft--;
+ sendGiveAudioStatus();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java b/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java
new file mode 100644
index 000000000000..438c1ea01e29
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java
@@ -0,0 +1,67 @@
+/*
+ * 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.server.hdmi;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceVolumeManager;
+import android.media.VolumeInfo;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Wrapper for {@link AudioDeviceVolumeManager}. Creates an instance of the class and directly
+ * passes method calls to that instance.
+ */
+public class AudioDeviceVolumeManagerWrapper
+ implements AudioDeviceVolumeManagerWrapperInterface {
+
+ private static final String TAG = "AudioDeviceVolumeManagerWrapper";
+
+ private final AudioDeviceVolumeManager mAudioDeviceVolumeManager;
+
+ public AudioDeviceVolumeManagerWrapper(Context context) {
+ mAudioDeviceVolumeManager = new AudioDeviceVolumeManager(context);
+ }
+
+ @Override
+ public void addOnDeviceVolumeBehaviorChangedListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener)
+ throws SecurityException {
+ mAudioDeviceVolumeManager.addOnDeviceVolumeBehaviorChangedListener(executor, listener);
+ }
+
+ @Override
+ public void removeOnDeviceVolumeBehaviorChangedListener(
+ @NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener) {
+ mAudioDeviceVolumeManager.removeOnDeviceVolumeBehaviorChangedListener(listener);
+ }
+
+ @Override
+ public void setDeviceAbsoluteVolumeBehavior(
+ @NonNull AudioDeviceAttributes device,
+ @NonNull VolumeInfo volume,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener,
+ boolean handlesVolumeAdjustment) {
+ mAudioDeviceVolumeManager.setDeviceAbsoluteVolumeBehavior(device, volume, executor,
+ vclistener, handlesVolumeAdjustment);
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapperInterface.java b/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapperInterface.java
new file mode 100644
index 000000000000..1a1d4c19358b
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapperInterface.java
@@ -0,0 +1,61 @@
+/*
+ * 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.server.hdmi;
+
+import static android.media.AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener;
+import static android.media.AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceVolumeManager;
+import android.media.VolumeInfo;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Interface with the methods from {@link AudioDeviceVolumeManager} used by the HDMI framework.
+ * Allows the class to be faked for tests.
+ */
+public interface AudioDeviceVolumeManagerWrapperInterface {
+
+ /**
+ * Wrapper for {@link AudioDeviceVolumeManager#addOnDeviceVolumeBehaviorChangedListener(
+ * Executor, OnDeviceVolumeBehaviorChangedListener)}
+ */
+ void addOnDeviceVolumeBehaviorChangedListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener);
+
+ /**
+ * Wrapper for {@link AudioDeviceVolumeManager#removeOnDeviceVolumeBehaviorChangedListener(
+ * OnDeviceVolumeBehaviorChangedListener)}
+ */
+ void removeOnDeviceVolumeBehaviorChangedListener(
+ @NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener);
+
+ /**
+ * Wrapper for {@link AudioDeviceVolumeManager#setDeviceAbsoluteVolumeBehavior(
+ * AudioDeviceAttributes, VolumeInfo, Executor, OnAudioDeviceVolumeChangedListener, boolean)}
+ */
+ void setDeviceAbsoluteVolumeBehavior(
+ @NonNull AudioDeviceAttributes device,
+ @NonNull VolumeInfo volume,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener,
+ boolean handlesVolumeAdjustment);
+}
diff --git a/services/core/java/com/android/server/hdmi/AudioStatus.java b/services/core/java/com/android/server/hdmi/AudioStatus.java
new file mode 100644
index 000000000000..a884ffb93a6d
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/AudioStatus.java
@@ -0,0 +1,67 @@
+/*
+ * 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.server.hdmi;
+
+import android.annotation.Nullable;
+
+import java.util.Objects;
+
+/**
+ * Immutable representation of the information in the [Audio Status] operand:
+ * volume status (0 <= N <= 100) and mute status (muted or unmuted).
+ */
+public class AudioStatus {
+ public static final int MAX_VOLUME = 100;
+ public static final int MIN_VOLUME = 0;
+
+ int mVolume;
+ boolean mMute;
+
+ public AudioStatus(int volume, boolean mute) {
+ mVolume = volume;
+ mMute = mute;
+ }
+
+ public int getVolume() {
+ return mVolume;
+ }
+
+ public boolean getMute() {
+ return mMute;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof AudioStatus)) {
+ return false;
+ }
+
+ AudioStatus other = (AudioStatus) obj;
+ return mVolume == other.mVolume
+ && mMute == other.mMute;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mVolume, mMute);
+ }
+
+ @Override
+ public String toString() {
+ return "AudioStatus mVolume:" + mVolume + " mMute:" + mMute;
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index 751f2db99528..157057d0e4b7 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -118,6 +118,7 @@ final class Constants {
MESSAGE_SYSTEM_AUDIO_MODE_REQUEST,
MESSAGE_GIVE_AUDIO_STATUS,
MESSAGE_SET_SYSTEM_AUDIO_MODE,
+ MESSAGE_SET_AUDIO_VOLUME_LEVEL,
MESSAGE_REPORT_AUDIO_STATUS,
MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS,
MESSAGE_SYSTEM_AUDIO_MODE_STATUS,
@@ -197,9 +198,9 @@ final class Constants {
static final int MESSAGE_SYSTEM_AUDIO_MODE_REQUEST = 0x70;
static final int MESSAGE_GIVE_AUDIO_STATUS = 0x71;
static final int MESSAGE_SET_SYSTEM_AUDIO_MODE = 0x72;
+ static final int MESSAGE_SET_AUDIO_VOLUME_LEVEL = 0x73;
static final int MESSAGE_REPORT_AUDIO_STATUS = 0x7A;
static final int MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS = 0x7D;
- static final int MESSAGE_SET_AUDIO_VOLUME_LEVEL = 0x73;
static final int MESSAGE_SYSTEM_AUDIO_MODE_STATUS = 0x7E;
static final int MESSAGE_ROUTING_CHANGE = 0x80;
static final int MESSAGE_ROUTING_INFORMATION = 0x81;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 26a161323e4f..fb2d2ee08cbd 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -303,6 +303,13 @@ abstract class HdmiCecLocalDevice {
if (dispatchMessageToAction(message)) {
return Constants.HANDLED;
}
+
+ // If a message type has its own class, all valid messages of that type
+ // will be represented by an instance of that class.
+ if (message instanceof SetAudioVolumeLevelMessage) {
+ return handleSetAudioVolumeLevel((SetAudioVolumeLevelMessage) message);
+ }
+
switch (message.getOpcode()) {
case Constants.MESSAGE_ACTIVE_SOURCE:
return handleActiveSource(message);
@@ -637,6 +644,11 @@ abstract class HdmiCecLocalDevice {
return Constants.NOT_HANDLED;
}
+ @Constants.HandleMessageResult
+ protected int handleSetAudioVolumeLevel(SetAudioVolumeLevelMessage message) {
+ return Constants.NOT_HANDLED;
+ }
+
@Constants.RcProfile
protected abstract int getRcProfile();
@@ -1002,6 +1014,57 @@ abstract class HdmiCecLocalDevice {
action.start();
}
+ void addAvcAudioStatusAction(int targetAddress) {
+ if (!hasAction(AbsoluteVolumeAudioStatusAction.class)) {
+ addAndStartAction(new AbsoluteVolumeAudioStatusAction(this, targetAddress));
+ }
+ }
+
+ void removeAvcAudioStatusAction() {
+ removeAction(AbsoluteVolumeAudioStatusAction.class);
+ }
+
+ void updateAvcVolume(int volumeIndex) {
+ for (AbsoluteVolumeAudioStatusAction action :
+ getActions(AbsoluteVolumeAudioStatusAction.class)) {
+ action.updateVolume(volumeIndex);
+ }
+ }
+
+ /**
+ * Determines whether {@code targetAddress} supports <Set Audio Volume Level>. Does two things
+ * in parallel: send <Give Features> (to get <Report Features> in response),
+ * and send <Set Audio Volume Level> (to see if it gets a <Feature Abort> in response).
+ */
+ @ServiceThreadOnly
+ void queryAvcSupport(int targetAddress) {
+ assertRunOnServiceThread();
+
+ // Send <Give Features> if using CEC 2.0 or above.
+ if (mService.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) {
+ synchronized (mLock) {
+ mService.sendCecCommand(HdmiCecMessageBuilder.buildGiveFeatures(
+ getDeviceInfo().getLogicalAddress(), targetAddress));
+ }
+ }
+
+ // If we don't already have a {@link SetAudioVolumeLevelDiscoveryAction} for the target
+ // device, start one.
+ List<SetAudioVolumeLevelDiscoveryAction> savlDiscoveryActions =
+ getActions(SetAudioVolumeLevelDiscoveryAction.class);
+ if (savlDiscoveryActions.stream().noneMatch(a -> a.getTargetAddress() == targetAddress)) {
+ addAndStartAction(new SetAudioVolumeLevelDiscoveryAction(this, targetAddress,
+ new IHdmiControlCallback.Stub() {
+ @Override
+ public void onComplete(int result) {
+ if (result == HdmiControlManager.RESULT_SUCCESS) {
+ getService().checkAndUpdateAbsoluteVolumeControlState();
+ }
+ }
+ }));
+ }
+ }
+
@ServiceThreadOnly
void startQueuedActions() {
assertRunOnServiceThread();
@@ -1205,6 +1268,9 @@ abstract class HdmiCecLocalDevice {
*/
protected void disableDevice(
boolean initiatedByCec, final PendingActionClearedCallback originalCallback) {
+ removeAction(AbsoluteVolumeAudioStatusAction.class);
+ removeAction(SetAudioVolumeLevelDiscoveryAction.class);
+
mPendingActionClearedCallback =
new PendingActionClearedCallback() {
@Override
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
index 90b4f76fec10..c0c02027a7a1 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -307,6 +307,7 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice {
protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
removeAction(OneTouchPlayAction.class);
removeAction(DevicePowerStatusAction.class);
+ removeAction(AbsoluteVolumeAudioStatusAction.class);
super.disableDevice(initiatedByCec, callback);
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 9212fb604721..1ea1457439ec 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -1166,6 +1166,19 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
return Constants.HANDLED;
}
+ @Override
+ @Constants.HandleMessageResult
+ protected int handleSetAudioVolumeLevel(SetAudioVolumeLevelMessage message) {
+ // <Set Audio Volume Level> should only be sent to the System Audio device, so we don't
+ // handle it when System Audio Mode is enabled.
+ if (mService.isSystemAudioActivated()) {
+ return Constants.ABORT_NOT_IN_CORRECT_MODE;
+ } else {
+ mService.setStreamMusicVolume(message.getAudioVolumeLevel(), 0);
+ return Constants.HANDLED;
+ }
+ }
+
void announceOneTouchRecordResult(int recorderAddress, int result) {
mService.invokeOneTouchRecordResult(recorderAddress, result);
}
@@ -1202,6 +1215,13 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
return mService.getHdmiCecNetwork().getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
}
+ /**
+ * Returns the audio output device used for System Audio Mode.
+ */
+ AudioDeviceAttributes getSystemAudioOutputDevice() {
+ return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI_ARC;
+ }
+
@ServiceThreadOnly
void handleRemoveActiveRoutingPath(int path) {
@@ -1296,6 +1316,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
removeAction(OneTouchRecordAction.class);
removeAction(TimerRecordingAction.class);
removeAction(NewDeviceAction.class);
+ removeAction(AbsoluteVolumeAudioStatusAction.class);
disableSystemAudioIfExist();
disableArcIfExist();
@@ -1318,7 +1339,6 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
removeAction(SystemAudioActionFromAvr.class);
removeAction(SystemAudioActionFromTv.class);
removeAction(SystemAudioAutoInitiationAction.class);
- removeAction(SystemAudioStatusAction.class);
removeAction(VolumeControlAction.class);
if (!mService.isControlEnabled()) {
@@ -1589,6 +1609,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
return DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder()
.setRecordTvScreenSupport(FEATURE_SUPPORTED)
.setArcTxSupport(hasArcPort ? FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED)
+ .setSetAudioVolumeLevelSupport(FEATURE_SUPPORTED)
.build();
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessage.java b/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
index 290cae50f2cb..2b84225e7c55 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
@@ -262,6 +262,8 @@ public class HdmiCecMessage {
return "Give Audio Status";
case Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE:
return "Set System Audio Mode";
+ case Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL:
+ return "Set Audio Volume Level";
case Constants.MESSAGE_REPORT_AUDIO_STATUS:
return "Report Audio Status";
case Constants.MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS:
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
index 8b6d16a171f5..caaf80073de1 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
@@ -259,6 +259,7 @@ public class HdmiCecNetwork {
// The addition of a local device should not notify listeners
return;
}
+ mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState();
if (info.getPhysicalAddress() == HdmiDeviceInfo.PATH_INVALID) {
// Don't notify listeners of devices that haven't reported their physical address yet
return;
@@ -383,7 +384,7 @@ public class HdmiCecNetwork {
final void removeCecDevice(HdmiCecLocalDevice localDevice, int address) {
assertRunOnServiceThread();
HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address));
-
+ mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState();
localDevice.mCecMessageCache.flushMessagesFrom(address);
if (info.getPhysicalAddress() == HdmiDeviceInfo.PATH_INVALID) {
// Don't notify listeners of devices that haven't reported their physical address yet
@@ -586,6 +587,8 @@ public class HdmiCecNetwork {
.build();
updateCecDevice(newDeviceInfo);
+
+ mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState();
}
@ServiceThreadOnly
@@ -617,6 +620,8 @@ public class HdmiCecNetwork {
)
.build();
updateCecDevice(newDeviceInfo);
+
+ mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState();
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 12380abd0d38..9824b4e6c43a 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -30,6 +30,7 @@ import static com.android.server.hdmi.Constants.OPTION_MHL_SERVICE_CONTROL;
import static com.android.server.power.ShutdownThread.SHUTDOWN_ACTION_PROPERTY;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -38,6 +39,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.database.ContentObserver;
import android.hardware.display.DisplayManager;
+import android.hardware.hdmi.DeviceFeatures;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiHotplugEvent;
@@ -56,7 +58,12 @@ import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
import android.hardware.hdmi.IHdmiVendorCommandListener;
import android.hardware.tv.cec.V1_0.OptionKey;
import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
+import android.media.AudioDeviceVolumeManager;
import android.media.AudioManager;
+import android.media.VolumeInfo;
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
import android.media.tv.TvInputManager;
@@ -83,6 +90,7 @@ import android.util.ArrayMap;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
+import android.view.KeyEvent;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -101,6 +109,7 @@ import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -202,6 +211,27 @@ public class HdmiControlService extends SystemService {
public @interface WakeReason {
}
+ @VisibleForTesting
+ static final AudioDeviceAttributes AUDIO_OUTPUT_DEVICE_HDMI = new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_HDMI, "");
+ @VisibleForTesting
+ static final AudioDeviceAttributes AUDIO_OUTPUT_DEVICE_HDMI_ARC =
+ new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_HDMI_ARC, "");
+ static final AudioDeviceAttributes AUDIO_OUTPUT_DEVICE_HDMI_EARC =
+ new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_HDMI_EARC, "");
+
+ // Audio output devices used for Absolute Volume Control
+ private static final List<AudioDeviceAttributes> AVC_AUDIO_OUTPUT_DEVICES =
+ Collections.unmodifiableList(Arrays.asList(AUDIO_OUTPUT_DEVICE_HDMI,
+ AUDIO_OUTPUT_DEVICE_HDMI_ARC, AUDIO_OUTPUT_DEVICE_HDMI_EARC));
+
+ // AudioAttributes for STREAM_MUSIC
+ @VisibleForTesting
+ static final AudioAttributes STREAM_MUSIC_ATTRIBUTES =
+ new AudioAttributes.Builder().setLegacyStreamType(AudioManager.STREAM_MUSIC).build();
+
private final Executor mServiceThreadExecutor = new Executor() {
@Override
public void execute(Runnable r) {
@@ -209,6 +239,10 @@ public class HdmiControlService extends SystemService {
}
};
+ Executor getServiceThreadExecutor() {
+ return mServiceThreadExecutor;
+ }
+
// Logical address of the active source.
@GuardedBy("mLock")
protected final ActiveSource mActiveSource = new ActiveSource();
@@ -222,6 +256,13 @@ public class HdmiControlService extends SystemService {
@HdmiControlManager.VolumeControl
private int mHdmiCecVolumeControl;
+ // Caches the volume behaviors of all audio output devices in AVC_AUDIO_OUTPUT_DEVICES.
+ @GuardedBy("mLock")
+ private Map<AudioDeviceAttributes, Integer> mAudioDeviceVolumeBehaviors = new HashMap<>();
+
+ // Maximum volume of AudioManager.STREAM_MUSIC. Set upon gaining access to system services.
+ private int mStreamMusicMaxVolume;
+
// Make sure HdmiCecConfig is instantiated and the XMLs are read.
private HdmiCecConfig mHdmiCecConfig;
@@ -411,6 +452,12 @@ public class HdmiControlService extends SystemService {
private PowerManagerInternalWrapper mPowerManagerInternal;
@Nullable
+ private AudioManager mAudioManager;
+
+ @Nullable
+ private AudioDeviceVolumeManagerWrapperInterface mAudioDeviceVolumeManager;
+
+ @Nullable
private Looper mIoLooper;
@Nullable
@@ -439,11 +486,21 @@ public class HdmiControlService extends SystemService {
private final SelectRequestBuffer mSelectRequestBuffer = new SelectRequestBuffer();
- @VisibleForTesting HdmiControlService(Context context, List<Integer> deviceTypes) {
+ /**
+ * Constructor for testing.
+ *
+ * It's critical to use a fake AudioDeviceVolumeManager because a normally instantiated
+ * AudioDeviceVolumeManager can access the "real" AudioService on the DUT.
+ *
+ * @see FakeAudioDeviceVolumeManagerWrapper
+ */
+ @VisibleForTesting HdmiControlService(Context context, List<Integer> deviceTypes,
+ AudioDeviceVolumeManagerWrapperInterface audioDeviceVolumeManager) {
super(context);
mLocalDevices = deviceTypes;
mSettingsObserver = new SettingsObserver(mHandler);
mHdmiCecConfig = new HdmiCecConfig(context);
+ mAudioDeviceVolumeManager = audioDeviceVolumeManager;
}
public HdmiControlService(Context context) {
@@ -744,6 +801,14 @@ public class HdmiControlService extends SystemService {
Context.TV_INPUT_SERVICE);
mPowerManager = new PowerManagerWrapper(getContext());
mPowerManagerInternal = new PowerManagerInternalWrapper();
+ mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+ mStreamMusicMaxVolume = getAudioManager().getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ if (mAudioDeviceVolumeManager == null) {
+ mAudioDeviceVolumeManager =
+ new AudioDeviceVolumeManagerWrapper(getContext());
+ }
+ getAudioDeviceVolumeManager().addOnDeviceVolumeBehaviorChangedListener(
+ mServiceThreadExecutor, this::onDeviceVolumeBehaviorChanged);
} else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
runOnServiceThread(this::bootCompleted);
}
@@ -2419,6 +2484,7 @@ public class HdmiControlService extends SystemService {
pw.println("mPowerStatus: " + mPowerStatusController.getPowerStatus());
pw.println("mIsCecAvailable: " + mIsCecAvailable);
pw.println("mCecVersion: " + mCecVersion);
+ pw.println("mIsAbsoluteVolumeControlEnabled: " + isAbsoluteVolumeControlEnabled());
// System settings
pw.println("System_settings:");
@@ -2578,6 +2644,7 @@ public class HdmiControlService extends SystemService {
@HdmiControlManager.VolumeControl int hdmiCecVolumeControl) {
mHdmiCecVolumeControl = hdmiCecVolumeControl;
announceHdmiCecVolumeControlFeatureChange(hdmiCecVolumeControl);
+ runOnServiceThread(this::checkAndUpdateAbsoluteVolumeControlState);
}
// Get the source address to send out commands to devices connected to the current device
@@ -3086,15 +3153,17 @@ public class HdmiControlService extends SystemService {
private void announceHdmiCecVolumeControlFeatureChange(
@HdmiControlManager.VolumeControl int hdmiCecVolumeControl) {
assertRunOnServiceThread();
- mHdmiCecVolumeControlFeatureListenerRecords.broadcast(listener -> {
- try {
- listener.onHdmiCecVolumeControlFeature(hdmiCecVolumeControl);
- } catch (RemoteException e) {
- Slog.e(TAG,
- "Failed to report HdmiControlVolumeControlStatusChange: "
- + hdmiCecVolumeControl);
- }
- });
+ synchronized (mLock) {
+ mHdmiCecVolumeControlFeatureListenerRecords.broadcast(listener -> {
+ try {
+ listener.onHdmiCecVolumeControlFeature(hdmiCecVolumeControl);
+ } catch (RemoteException e) {
+ Slog.e(TAG,
+ "Failed to report HdmiControlVolumeControlStatusChange: "
+ + hdmiCecVolumeControl);
+ }
+ });
+ }
}
public HdmiCecLocalDeviceTv tv() {
@@ -3131,8 +3200,20 @@ public class HdmiControlService extends SystemService {
HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
}
+ /**
+ * Returns null before the boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}.
+ */
+ @Nullable
AudioManager getAudioManager() {
- return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+ return mAudioManager;
+ }
+
+ /**
+ * Returns null before the boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}.
+ */
+ @Nullable
+ private AudioDeviceVolumeManagerWrapperInterface getAudioDeviceVolumeManager() {
+ return mAudioDeviceVolumeManager;
}
boolean isControlEnabled() {
@@ -3486,6 +3567,7 @@ public class HdmiControlService extends SystemService {
synchronized (mLock) {
mSystemAudioActivated = on;
}
+ runOnServiceThread(this::checkAndUpdateAbsoluteVolumeControlState);
}
@ServiceThreadOnly
@@ -3599,6 +3681,8 @@ public class HdmiControlService extends SystemService {
device.addActiveSourceHistoryItem(new ActiveSource(logicalAddress, physicalAddress),
deviceIsActiveSource, caller);
}
+
+ runOnServiceThread(this::checkAndUpdateAbsoluteVolumeControlState);
}
// This method should only be called when the device can be the active source
@@ -3791,4 +3875,332 @@ public class HdmiControlService extends SystemService {
Slog.e(TAG, "Failed to report setting change", e);
}
}
+
+ /**
+ * Listener for changes to the volume behavior of an audio output device. Caches the
+ * volume behavior of devices used for Absolute Volume Control.
+ */
+ @VisibleForTesting
+ @ServiceThreadOnly
+ void onDeviceVolumeBehaviorChanged(AudioDeviceAttributes device, int volumeBehavior) {
+ assertRunOnServiceThread();
+ if (AVC_AUDIO_OUTPUT_DEVICES.contains(device)) {
+ synchronized (mLock) {
+ mAudioDeviceVolumeBehaviors.put(device, volumeBehavior);
+ }
+ checkAndUpdateAbsoluteVolumeControlState();
+ }
+ }
+
+ /**
+ * Wrapper for {@link AudioManager#getDeviceVolumeBehavior} that takes advantage of cached
+ * results for the volume behaviors of HDMI audio devices.
+ */
+ @AudioManager.DeviceVolumeBehavior
+ private int getDeviceVolumeBehavior(AudioDeviceAttributes device) {
+ if (AVC_AUDIO_OUTPUT_DEVICES.contains(device)) {
+ synchronized (mLock) {
+ if (mAudioDeviceVolumeBehaviors.containsKey(device)) {
+ return mAudioDeviceVolumeBehaviors.get(device);
+ }
+ }
+ }
+ return getAudioManager().getDeviceVolumeBehavior(device);
+ }
+
+ /**
+ * Returns whether Absolute Volume Control is enabled or not. This is determined by the
+ * volume behavior of the relevant HDMI audio output device(s) for this device's type.
+ */
+ public boolean isAbsoluteVolumeControlEnabled() {
+ if (!isTvDevice() && !isPlaybackDevice()) {
+ return false;
+ }
+ AudioDeviceAttributes avcAudioOutputDevice = getAvcAudioOutputDevice();
+ if (avcAudioOutputDevice == null) {
+ return false;
+ }
+ return getDeviceVolumeBehavior(avcAudioOutputDevice)
+ == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE;
+ }
+
+ private AudioDeviceAttributes getAvcAudioOutputDevice() {
+ if (isTvDevice()) {
+ return tv().getSystemAudioOutputDevice();
+ } else if (isPlaybackDevice()) {
+ return AUDIO_OUTPUT_DEVICE_HDMI;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Checks the conditions for Absolute Volume Control (AVC), and enables or disables the feature
+ * if necessary. AVC is enabled precisely when a specific audio output device
+ * (HDMI for playback devices, and HDMI_ARC or HDMI_EARC for TVs) is using absolute volume
+ * behavior.
+ *
+ * AVC must be enabled on a Playback device or TV precisely when it is playing
+ * audio on an external device (the System Audio device) that supports the feature.
+ * This reduces to these conditions:
+ *
+ * 1. If the System Audio Device is an Audio System: System Audio Mode is active
+ * 2. Our HDMI audio output device is using full volume behavior
+ * 3. CEC volume is enabled
+ * 4. The System Audio device supports AVC (i.e. it supports <Set Audio Volume Level>)
+ *
+ * If not all of these conditions are met, this method disables AVC if necessary.
+ *
+ * If all of these conditions are met, this method starts an action to query the System Audio
+ * device's audio status, which enables AVC upon obtaining the audio status.
+ */
+ @ServiceThreadOnly
+ void checkAndUpdateAbsoluteVolumeControlState() {
+ assertRunOnServiceThread();
+
+ // Can't enable or disable AVC before we have access to system services
+ if (getAudioManager() == null) {
+ return;
+ }
+
+ HdmiCecLocalDevice localCecDevice;
+ if (isTvDevice() && tv() != null) {
+ localCecDevice = tv();
+ // Condition 1: TVs need System Audio Mode to be active
+ // (Doesn't apply to Playback Devices, where if SAM isn't active, we assume the
+ // TV is the System Audio Device instead.)
+ if (!isSystemAudioActivated()) {
+ disableAbsoluteVolumeControl();
+ return;
+ }
+ } else if (isPlaybackDevice() && playback() != null) {
+ localCecDevice = playback();
+ } else {
+ // Either this device type doesn't support AVC, or it hasn't fully initialized yet
+ return;
+ }
+
+ HdmiDeviceInfo systemAudioDeviceInfo = getHdmiCecNetwork().getSafeCecDeviceInfo(
+ localCecDevice.findAudioReceiverAddress());
+ @AudioManager.DeviceVolumeBehavior int currentVolumeBehavior =
+ getDeviceVolumeBehavior(getAvcAudioOutputDevice());
+
+ // Condition 2: Already using full or absolute volume behavior
+ boolean alreadyUsingFullOrAbsoluteVolume =
+ currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL
+ || currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE;
+ // Condition 3: CEC volume is enabled
+ boolean cecVolumeEnabled =
+ getHdmiCecVolumeControl() == HdmiControlManager.VOLUME_CONTROL_ENABLED;
+
+ if (!cecVolumeEnabled || !alreadyUsingFullOrAbsoluteVolume) {
+ disableAbsoluteVolumeControl();
+ return;
+ }
+
+ // Check for safety: if the System Audio device is a candidate for AVC, we should already
+ // have received messages from it to trigger the other conditions.
+ if (systemAudioDeviceInfo == null) {
+ disableAbsoluteVolumeControl();
+ return;
+ }
+ // Condition 4: The System Audio device supports AVC (i.e. <Set Audio Volume Level>).
+ switch (systemAudioDeviceInfo.getDeviceFeatures().getSetAudioVolumeLevelSupport()) {
+ case DeviceFeatures.FEATURE_SUPPORTED:
+ if (!isAbsoluteVolumeControlEnabled()) {
+ // Start an action that will call {@link #enableAbsoluteVolumeControl}
+ // once the System Audio device sends <Report Audio Status>
+ localCecDevice.addAvcAudioStatusAction(
+ systemAudioDeviceInfo.getLogicalAddress());
+ }
+ return;
+ case DeviceFeatures.FEATURE_NOT_SUPPORTED:
+ disableAbsoluteVolumeControl();
+ return;
+ case DeviceFeatures.FEATURE_SUPPORT_UNKNOWN:
+ disableAbsoluteVolumeControl();
+ localCecDevice.queryAvcSupport(systemAudioDeviceInfo.getLogicalAddress());
+ return;
+ default:
+ return;
+ }
+ }
+
+ private void disableAbsoluteVolumeControl() {
+ if (isPlaybackDevice()) {
+ playback().removeAvcAudioStatusAction();
+ } else if (isTvDevice()) {
+ tv().removeAvcAudioStatusAction();
+ }
+ AudioDeviceAttributes device = getAvcAudioOutputDevice();
+ if (getDeviceVolumeBehavior(device) == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) {
+ getAudioManager().setDeviceVolumeBehavior(device,
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ }
+ }
+
+ /**
+ * Enables Absolute Volume Control. Should only be called when all the conditions for
+ * AVC are met (see {@link #checkAndUpdateAbsoluteVolumeControlState}).
+ * @param audioStatus The initial audio status to set the audio output device to
+ */
+ void enableAbsoluteVolumeControl(AudioStatus audioStatus) {
+ HdmiCecLocalDevice localDevice = isPlaybackDevice() ? playback() : tv();
+ HdmiDeviceInfo systemAudioDevice = getHdmiCecNetwork().getDeviceInfo(
+ localDevice.findAudioReceiverAddress());
+ VolumeInfo volumeInfo = new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
+ .setMuted(audioStatus.getMute())
+ .setVolumeIndex(audioStatus.getVolume())
+ .setMaxVolumeIndex(AudioStatus.MAX_VOLUME)
+ .setMinVolumeIndex(AudioStatus.MIN_VOLUME)
+ .build();
+ mAbsoluteVolumeChangedListener = new AbsoluteVolumeChangedListener(
+ localDevice, systemAudioDevice);
+
+ // AudioService sets the volume of the stream and device based on the input VolumeInfo
+ // when enabling absolute volume behavior, but not the mute state
+ notifyAvcMuteChange(audioStatus.getMute());
+ getAudioDeviceVolumeManager().setDeviceAbsoluteVolumeBehavior(
+ getAvcAudioOutputDevice(), volumeInfo, mServiceThreadExecutor,
+ mAbsoluteVolumeChangedListener, true);
+ }
+
+ private AbsoluteVolumeChangedListener mAbsoluteVolumeChangedListener;
+
+ @VisibleForTesting
+ AbsoluteVolumeChangedListener getAbsoluteVolumeChangedListener() {
+ return mAbsoluteVolumeChangedListener;
+ }
+
+ /**
+ * Listeners for changes reported by AudioService to the state of an audio output device using
+ * absolute volume behavior.
+ */
+ @VisibleForTesting
+ class AbsoluteVolumeChangedListener implements
+ AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener {
+ private HdmiCecLocalDevice mLocalDevice;
+ private HdmiDeviceInfo mSystemAudioDevice;
+
+ private AbsoluteVolumeChangedListener(HdmiCecLocalDevice localDevice,
+ HdmiDeviceInfo systemAudioDevice) {
+ mLocalDevice = localDevice;
+ mSystemAudioDevice = systemAudioDevice;
+ }
+
+ /**
+ * Called when AudioService sets the volume level of an absolute volume audio output device
+ * to a numeric value.
+ */
+ @Override
+ public void onAudioDeviceVolumeChanged(
+ @NonNull AudioDeviceAttributes audioDevice,
+ @NonNull VolumeInfo volumeInfo) {
+ int localDeviceAddress;
+ synchronized (mLocalDevice.mLock) {
+ localDeviceAddress = mLocalDevice.getDeviceInfo().getLogicalAddress();
+ }
+ sendCecCommand(SetAudioVolumeLevelMessage.build(
+ localDeviceAddress,
+ mSystemAudioDevice.getLogicalAddress(),
+ volumeInfo.getVolumeIndex()),
+ // If sending the message fails, ask the System Audio device for its
+ // audio status so that we can update AudioService
+ (int errorCode) -> {
+ if (errorCode == SendMessageResult.SUCCESS) {
+ // Update the volume tracked in our AbsoluteVolumeAudioStatusAction
+ // so it correctly processes incoming <Report Audio Status> messages
+ HdmiCecLocalDevice avcDevice = isTvDevice() ? tv() : playback();
+ avcDevice.updateAvcVolume(volumeInfo.getVolumeIndex());
+ } else {
+ sendCecCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(
+ localDeviceAddress,
+ mSystemAudioDevice.getLogicalAddress()
+ ));
+ }
+ });
+ }
+
+ /**
+ * Called when AudioService adjusts the volume or mute state of an absolute volume
+ * audio output device
+ */
+ @Override
+ public void onAudioDeviceVolumeAdjusted(
+ @NonNull AudioDeviceAttributes audioDevice,
+ @NonNull VolumeInfo volumeInfo,
+ @AudioManager.VolumeAdjustment int direction,
+ @AudioDeviceVolumeManager.VolumeAdjustmentMode int mode
+ ) {
+ int keyCode;
+ switch (direction) {
+ case AudioManager.ADJUST_RAISE:
+ keyCode = KeyEvent.KEYCODE_VOLUME_UP;
+ break;
+ case AudioManager.ADJUST_LOWER:
+ keyCode = KeyEvent.KEYCODE_VOLUME_DOWN;
+ break;
+ case AudioManager.ADJUST_TOGGLE_MUTE:
+ case AudioManager.ADJUST_MUTE:
+ case AudioManager.ADJUST_UNMUTE:
+ // Many CEC devices only support toggle mute. Therefore, we send the
+ // same keycode for all three mute options.
+ keyCode = KeyEvent.KEYCODE_VOLUME_MUTE;
+ break;
+ default:
+ return;
+ }
+ switch (mode) {
+ case AudioDeviceVolumeManager.ADJUST_MODE_NORMAL:
+ mLocalDevice.sendVolumeKeyEvent(keyCode, true);
+ mLocalDevice.sendVolumeKeyEvent(keyCode, false);
+ break;
+ case AudioDeviceVolumeManager.ADJUST_MODE_START:
+ mLocalDevice.sendVolumeKeyEvent(keyCode, true);
+ break;
+ case AudioDeviceVolumeManager.ADJUST_MODE_END:
+ mLocalDevice.sendVolumeKeyEvent(keyCode, false);
+ break;
+ default:
+ return;
+ }
+ }
+ }
+
+ /**
+ * Notifies AudioService of a change in the volume of the System Audio device. Has no effect if
+ * AVC is disabled, or the audio output device for AVC is not playing for STREAM_MUSIC
+ */
+ void notifyAvcVolumeChange(int volume) {
+ if (!isAbsoluteVolumeControlEnabled()) return;
+ List<AudioDeviceAttributes> streamMusicDevices =
+ getAudioManager().getDevicesForAttributes(STREAM_MUSIC_ATTRIBUTES);
+ if (streamMusicDevices.contains(getAvcAudioOutputDevice())) {
+ setStreamMusicVolume(volume, AudioManager.FLAG_ABSOLUTE_VOLUME);
+ }
+ }
+
+ /**
+ * Notifies AudioService of a change in the mute status of the System Audio device. Has no
+ * effect if AVC is disabled, or the audio output device for AVC is not playing for STREAM_MUSIC
+ */
+ void notifyAvcMuteChange(boolean mute) {
+ if (!isAbsoluteVolumeControlEnabled()) return;
+ List<AudioDeviceAttributes> streamMusicDevices =
+ getAudioManager().getDevicesForAttributes(STREAM_MUSIC_ATTRIBUTES);
+ if (streamMusicDevices.contains(getAvcAudioOutputDevice())) {
+ int direction = mute ? AudioManager.ADJUST_MUTE : AudioManager.ADJUST_UNMUTE;
+ getAudioManager().adjustStreamVolume(AudioManager.STREAM_MUSIC, direction,
+ AudioManager.FLAG_ABSOLUTE_VOLUME);
+ }
+ }
+
+ /**
+ * Sets the volume index of {@link AudioManager#STREAM_MUSIC}. Rescales the input volume index
+ * from HDMI-CEC volume range to STREAM_MUSIC's.
+ */
+ void setStreamMusicVolume(int volume, int flags) {
+ getAudioManager().setStreamVolume(AudioManager.STREAM_MUSIC,
+ volume * mStreamMusicMaxVolume / AudioStatus.MAX_VOLUME, flags);
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/SendKeyAction.java b/services/core/java/com/android/server/hdmi/SendKeyAction.java
index adcef667545f..7daeaf19c657 100644
--- a/services/core/java/com/android/server/hdmi/SendKeyAction.java
+++ b/services/core/java/com/android/server/hdmi/SendKeyAction.java
@@ -172,8 +172,19 @@ final class SendKeyAction extends HdmiCecFeatureAction {
}
private void sendKeyUp() {
- sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(),
- mTargetAddress));
+ // When using Absolute Volume Control, query audio status after a volume key is released.
+ // This allows us to notify AudioService of the resulting volume or mute status changes.
+ if (HdmiCecKeycode.isVolumeKeycode(mLastKeycode)
+ && localDevice().getService().isAbsoluteVolumeControlEnabled()) {
+ sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(),
+ mTargetAddress),
+ __ -> sendCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(
+ getSourceAddress(),
+ localDevice().findAudioReceiverAddress())));
+ } else {
+ sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(),
+ mTargetAddress));
+ }
}
@Override
diff --git a/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java b/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java
index 96fd003c72b4..eb3b33d8ca22 100644
--- a/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java
+++ b/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java
@@ -122,4 +122,11 @@ public class SetAudioVolumeLevelDiscoveryAction extends HdmiCecFeatureAction {
return true;
}
}
+
+ /**
+ * Returns the logical address of this action's target device.
+ */
+ public int getTargetAddress() {
+ return mTargetAddress;
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAction.java
index 978c25d0a8c6..e7a3db75c9d0 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioAction.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioAction.java
@@ -153,7 +153,7 @@ abstract class SystemAudioAction extends HdmiCecFeatureAction {
boolean receivedStatus = HdmiUtils.parseCommandParamSystemAudioStatus(cmd);
if (receivedStatus == mTargetAudioStatus) {
setSystemAudioMode(receivedStatus);
- startAudioStatusAction();
+ finish();
return true;
} else {
HdmiLogger.debug("Unexpected system audio mode request:" + receivedStatus);
@@ -168,11 +168,6 @@ abstract class SystemAudioAction extends HdmiCecFeatureAction {
}
}
- protected void startAudioStatusAction() {
- addAndStartAction(new SystemAudioStatusAction(tv(), mAvrLogicalAddress, mCallbacks));
- finish();
- }
-
protected void removeSystemAudioActionInProgress() {
removeActionExcept(SystemAudioActionFromTv.class, this);
removeActionExcept(SystemAudioActionFromAvr.class, this);
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
index 6ddff91a70f7..99148c4ea114 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
@@ -16,8 +16,8 @@
package com.android.server.hdmi;
-import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.IHdmiControlCallback;
/**
@@ -65,7 +65,7 @@ final class SystemAudioActionFromAvr extends SystemAudioAction {
if (mTargetAudioStatus) {
setSystemAudioMode(true);
- startAudioStatusAction();
+ finish();
} else {
setSystemAudioMode(false);
finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
deleted file mode 100644
index b4af540b96f5..000000000000
--- a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2014 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.hdmi;
-
-import android.hardware.hdmi.HdmiControlManager;
-import android.hardware.hdmi.IHdmiControlCallback;
-import android.hardware.tv.cec.V1_0.SendMessageResult;
-
-import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
-
-import java.util.List;
-
-/**
- * Action to update audio status (volume or mute) of audio amplifier
- */
-final class SystemAudioStatusAction extends HdmiCecFeatureAction {
- private static final String TAG = "SystemAudioStatusAction";
-
- // State that waits for <ReportAudioStatus>.
- private static final int STATE_WAIT_FOR_REPORT_AUDIO_STATUS = 1;
-
- private final int mAvrAddress;
-
- SystemAudioStatusAction(
- HdmiCecLocalDevice source, int avrAddress, List<IHdmiControlCallback> callbacks) {
- super(source, callbacks);
- mAvrAddress = avrAddress;
- }
-
- SystemAudioStatusAction(HdmiCecLocalDevice source, int avrAddress,
- IHdmiControlCallback callback) {
- super(source, callback);
- mAvrAddress = avrAddress;
- }
-
- @Override
- boolean start() {
- mState = STATE_WAIT_FOR_REPORT_AUDIO_STATUS;
- addTimer(mState, HdmiConfig.TIMEOUT_MS);
- sendGiveAudioStatus();
- return true;
- }
-
- private void sendGiveAudioStatus() {
- sendCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(getSourceAddress(), mAvrAddress),
- new SendMessageCallback() {
- @Override
- public void onSendCompleted(int error) {
- if (error != SendMessageResult.SUCCESS) {
- handleSendGiveAudioStatusFailure();
- }
- }
- });
- }
-
- private void handleSendGiveAudioStatusFailure() {
-
- // Still return SUCCESS to callback.
- finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
- }
-
- @Override
- boolean processCommand(HdmiCecMessage cmd) {
- if (mState != STATE_WAIT_FOR_REPORT_AUDIO_STATUS || mAvrAddress != cmd.getSource()) {
- return false;
- }
-
- switch (cmd.getOpcode()) {
- case Constants.MESSAGE_REPORT_AUDIO_STATUS:
- handleReportAudioStatus(cmd);
- return true;
- }
-
- return false;
- }
-
- private void handleReportAudioStatus(HdmiCecMessage cmd) {
- byte[] params = cmd.getParams();
- boolean mute = HdmiUtils.isAudioStatusMute(cmd);
- int volume = HdmiUtils.getAudioStatusVolume(cmd);
- tv().setAudioStatus(mute, volume);
-
- if (!(tv().isSystemAudioActivated() ^ mute)) {
- // Toggle AVR's mute status to match with the system audio status.
- sendUserControlPressedAndReleased(mAvrAddress, HdmiCecKeycode.CEC_KEYCODE_MUTE);
- }
- finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
- }
-
- @Override
- void handleTimerEvent(int state) {
- if (mState != state) {
- return;
- }
-
- handleSendGiveAudioStatusFailure();
- }
-}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 7360bbc5e54d..603d0128cc44 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -20,6 +20,7 @@ import static android.view.KeyEvent.KEYCODE_UNKNOWN;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManagerInternal;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -73,7 +74,6 @@ import android.os.InputEventInjectionSync;
import android.os.LocaleList;
import android.os.Looper;
import android.os.Message;
-import android.os.MessageQueue;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
@@ -111,6 +111,7 @@ import android.widget.Toast;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.SomeArgs;
@@ -146,9 +147,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.OptionalInt;
-/*
- * Wraps the C++ InputManager and provides its callbacks.
- */
+/** The system implementation of {@link IInputManager} that manages input devices. */
public class InputManagerService extends IInputManager.Stub
implements Watchdog.Monitor {
static final String TAG = "InputManager";
@@ -182,8 +181,7 @@ public class InputManagerService extends IInputManager.Stub
/** TODO(b/169067926): Remove this. */
private static final boolean UNTRUSTED_TOUCHES_TOAST = false;
- // Pointer to native input manager service object.
- private final long mPtr;
+ private final NativeInputManagerService mNative;
private final Context mContext;
private final InputManagerHandler mHandler;
@@ -298,92 +296,6 @@ public class InputManagerService extends IInputManager.Stub
@GuardedBy("mInputMonitors")
final Map<IBinder, GestureMonitorSpyWindow> mInputMonitors = new HashMap<>();
- private static native long nativeInit(InputManagerService service,
- Context context, MessageQueue messageQueue);
- private static native void nativeStart(long ptr);
- private static native void nativeSetDisplayViewports(long ptr,
- DisplayViewport[] viewports);
-
- private static native int nativeGetScanCodeState(long ptr,
- int deviceId, int sourceMask, int scanCode);
- private static native int nativeGetKeyCodeState(long ptr,
- int deviceId, int sourceMask, int keyCode);
- private static native int nativeGetSwitchState(long ptr,
- int deviceId, int sourceMask, int sw);
- private static native boolean nativeHasKeys(long ptr,
- int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists);
- private static native int nativeGetKeyCodeForKeyLocation(long ptr, int deviceId,
- int locationKeyCode);
- private static native InputChannel nativeCreateInputChannel(long ptr, String name);
- private static native InputChannel nativeCreateInputMonitor(long ptr, int displayId,
- String name, int pid);
- private static native void nativeRemoveInputChannel(long ptr, IBinder connectionToken);
- private static native void nativePilferPointers(long ptr, IBinder token);
- private static native void nativeSetInputFilterEnabled(long ptr, boolean enable);
- private static native boolean nativeSetInTouchMode(long ptr, boolean inTouchMode, int pid,
- int uid, boolean hasPermission);
- private static native void nativeSetMaximumObscuringOpacityForTouch(long ptr, float opacity);
- private static native void nativeSetBlockUntrustedTouchesMode(long ptr, int mode);
- private static native int nativeInjectInputEvent(long ptr, InputEvent event,
- int injectorPid, int injectorUid, int syncMode, int timeoutMillis,
- int policyFlags);
- private static native VerifiedInputEvent nativeVerifyInputEvent(long ptr, InputEvent event);
- private static native void nativeToggleCapsLock(long ptr, int deviceId);
- private static native void nativeDisplayRemoved(long ptr, int displayId);
- private static native void nativeSetInputDispatchMode(long ptr, boolean enabled, boolean frozen);
- private static native void nativeSetSystemUiLightsOut(long ptr, boolean lightsOut);
- private static native void nativeSetFocusedApplication(long ptr,
- int displayId, InputApplicationHandle application);
- private static native void nativeSetFocusedDisplay(long ptr, int displayId);
- private static native boolean nativeTransferTouchFocus(long ptr,
- IBinder fromChannelToken, IBinder toChannelToken, boolean isDragDrop);
- private static native boolean nativeTransferTouch(long ptr, IBinder destChannelToken);
- private static native void nativeSetPointerSpeed(long ptr, int speed);
- private static native void nativeSetPointerAcceleration(long ptr, float acceleration);
- private static native void nativeSetShowTouches(long ptr, boolean enabled);
- private static native void nativeSetInteractive(long ptr, boolean interactive);
- private static native void nativeReloadCalibration(long ptr);
- private static native void nativeVibrate(long ptr, int deviceId, long[] pattern,
- int[] amplitudes, int repeat, int token);
- private static native void nativeVibrateCombined(long ptr, int deviceId, long[] pattern,
- SparseArray<int[]> amplitudes, int repeat, int token);
- private static native void nativeCancelVibrate(long ptr, int deviceId, int token);
- private static native boolean nativeIsVibrating(long ptr, int deviceId);
- private static native int[] nativeGetVibratorIds(long ptr, int deviceId);
- private static native int nativeGetBatteryCapacity(long ptr, int deviceId);
- private static native int nativeGetBatteryStatus(long ptr, int deviceId);
- private static native List<Light> nativeGetLights(long ptr, int deviceId);
- private static native int nativeGetLightPlayerId(long ptr, int deviceId, int lightId);
- private static native int nativeGetLightColor(long ptr, int deviceId, int lightId);
- private static native void nativeSetLightPlayerId(long ptr, int deviceId, int lightId,
- int playerId);
- private static native void nativeSetLightColor(long ptr, int deviceId, int lightId, int color);
- private static native void nativeReloadKeyboardLayouts(long ptr);
- private static native void nativeReloadDeviceAliases(long ptr);
- private static native String nativeDump(long ptr);
- private static native void nativeMonitor(long ptr);
- private static native boolean nativeIsInputDeviceEnabled(long ptr, int deviceId);
- private static native void nativeEnableInputDevice(long ptr, int deviceId);
- private static native void nativeDisableInputDevice(long ptr, int deviceId);
- private static native void nativeSetPointerIconType(long ptr, int iconId);
- private static native void nativeReloadPointerIcons(long ptr);
- private static native void nativeSetCustomPointerIcon(long ptr, PointerIcon icon);
- private static native void nativeRequestPointerCapture(long ptr, IBinder windowToken,
- boolean enabled);
- private static native boolean nativeCanDispatchToDisplay(long ptr, int deviceId, int displayId);
- private static native void nativeNotifyPortAssociationsChanged(long ptr);
- private static native void nativeChangeUniqueIdAssociation(long ptr);
- private static native void nativeNotifyPointerDisplayIdChanged(long ptr);
- private static native void nativeSetDisplayEligibilityForPointerCapture(long ptr, int displayId,
- boolean enabled);
- private static native void nativeSetMotionClassifierEnabled(long ptr, boolean enabled);
- private static native InputSensorInfo[] nativeGetSensorList(long ptr, int deviceId);
- private static native boolean nativeFlushSensor(long ptr, int deviceId, int sensorType);
- private static native boolean nativeEnableSensor(long ptr, int deviceId, int sensorType,
- int samplingPeriodUs, int maxBatchReportLatencyUs);
- private static native void nativeDisableSensor(long ptr, int deviceId, int sensorType);
- private static native void nativeCancelCurrentTouch(long ptr);
-
// Maximum number of milliseconds to wait for input event injection.
private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000;
@@ -450,18 +362,47 @@ public class InputManagerService extends IInputManager.Stub
/** Whether to use the dev/input/event or uevent subsystem for the audio jack. */
final boolean mUseDevInputEventForAudioJack;
+ /** Point of injection for test dependencies. */
+ @VisibleForTesting
+ static class Injector {
+ private final Context mContext;
+ private final Looper mLooper;
+
+ Injector(Context context, Looper looper) {
+ mContext = context;
+ mLooper = looper;
+ }
+
+ Context getContext() {
+ return mContext;
+ }
+
+ Looper getLooper() {
+ return mLooper;
+ }
+
+ NativeInputManagerService getNativeService(InputManagerService service) {
+ return new NativeInputManagerService.NativeImpl(service, mContext, mLooper.getQueue());
+ }
+ }
+
public InputManagerService(Context context) {
- this.mContext = context;
- this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());
+ this(new Injector(context, DisplayThread.get().getLooper()));
+ }
+
+ @VisibleForTesting
+ InputManagerService(Injector injector) {
+ mContext = injector.getContext();
+ mHandler = new InputManagerHandler(injector.getLooper());
+ mNative = injector.getNativeService(this);
mStaticAssociations = loadStaticInputPortAssociations();
mUseDevInputEventForAudioJack =
- context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
+ mContext.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="
+ mUseDevInputEventForAudioJack);
- mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
- String doubleTouchGestureEnablePath = context.getResources().getString(
+ String doubleTouchGestureEnablePath = mContext.getResources().getString(
R.string.config_doubleTouchGestureEnableFile);
mDoubleTouchGestureEnableFile = TextUtils.isEmpty(doubleTouchGestureEnablePath) ? null :
new File(doubleTouchGestureEnablePath);
@@ -504,9 +445,9 @@ public class InputManagerService extends IInputManager.Stub
public void start() {
Slog.i(TAG, "Starting input manager");
- nativeStart(mPtr);
+ mNative.start();
- // Add ourself to the Watchdog monitors.
+ // Add ourselves to the Watchdog monitors.
Watchdog.getInstance().addMonitor(this);
registerPointerSpeedSettingObserver();
@@ -599,14 +540,14 @@ public class InputManagerService extends IInputManager.Stub
if (DEBUG) {
Slog.d(TAG, "Reloading keyboard layouts.");
}
- nativeReloadKeyboardLayouts(mPtr);
+ mNative.reloadKeyboardLayouts();
}
private void reloadDeviceAliases() {
if (DEBUG) {
Slog.d(TAG, "Reloading device names.");
}
- nativeReloadDeviceAliases(mPtr);
+ mNative.reloadDeviceAliases();
}
private void setDisplayViewportsInternal(List<DisplayViewport> viewports) {
@@ -615,7 +556,7 @@ public class InputManagerService extends IInputManager.Stub
for (int i = viewports.size() - 1; i >= 0; --i) {
vArray[i] = viewports.get(i);
}
- nativeSetDisplayViewports(mPtr, vArray);
+ mNative.setDisplayViewports(vArray);
if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) {
final AdditionalDisplayInputProperties properties =
@@ -642,7 +583,7 @@ public class InputManagerService extends IInputManager.Stub
* @return The key state.
*/
public int getKeyCodeState(int deviceId, int sourceMask, int keyCode) {
- return nativeGetKeyCodeState(mPtr, deviceId, sourceMask, keyCode);
+ return mNative.getKeyCodeState(deviceId, sourceMask, keyCode);
}
/**
@@ -655,7 +596,7 @@ public class InputManagerService extends IInputManager.Stub
* @return The key state.
*/
public int getScanCodeState(int deviceId, int sourceMask, int scanCode) {
- return nativeGetScanCodeState(mPtr, deviceId, sourceMask, scanCode);
+ return mNative.getScanCodeState(deviceId, sourceMask, scanCode);
}
/**
@@ -668,7 +609,7 @@ public class InputManagerService extends IInputManager.Stub
* @return The switch state.
*/
public int getSwitchState(int deviceId, int sourceMask, int switchCode) {
- return nativeGetSwitchState(mPtr, deviceId, sourceMask, switchCode);
+ return mNative.getSwitchState(deviceId, sourceMask, switchCode);
}
/**
@@ -691,7 +632,7 @@ public class InputManagerService extends IInputManager.Stub
throw new IllegalArgumentException("keyExists must be at least as large as keyCodes");
}
- return nativeHasKeys(mPtr, deviceId, sourceMask, keyCodes, keyExists);
+ return mNative.hasKeys(deviceId, sourceMask, keyCodes, keyExists);
}
/**
@@ -707,7 +648,7 @@ public class InputManagerService extends IInputManager.Stub
if (locationKeyCode <= KEYCODE_UNKNOWN || locationKeyCode > KeyEvent.getMaxKeyCode()) {
return KEYCODE_UNKNOWN;
}
- return nativeGetKeyCodeForKeyLocation(mPtr, deviceId, locationKeyCode);
+ return mNative.getKeyCodeForKeyLocation(deviceId, locationKeyCode);
}
/**
@@ -720,7 +661,7 @@ public class InputManagerService extends IInputManager.Stub
public boolean transferTouch(IBinder destChannelToken) {
// TODO(b/162194035): Replace this with a SPY window
Objects.requireNonNull(destChannelToken, "destChannelToken must not be null");
- return nativeTransferTouch(mPtr, destChannelToken);
+ return mNative.transferTouch(destChannelToken);
}
/**
@@ -736,7 +677,7 @@ public class InputManagerService extends IInputManager.Stub
throw new IllegalArgumentException("displayId must >= 0.");
}
- return nativeCreateInputMonitor(mPtr, displayId, inputChannelName, Binder.getCallingPid());
+ return mNative.createInputMonitor(displayId, inputChannelName, Binder.getCallingPid());
}
@NonNull
@@ -818,7 +759,7 @@ public class InputManagerService extends IInputManager.Stub
* @param name The name of this input channel
*/
public InputChannel createInputChannel(String name) {
- return nativeCreateInputChannel(mPtr, name);
+ return mNative.createInputChannel(name);
}
/**
@@ -827,7 +768,7 @@ public class InputManagerService extends IInputManager.Stub
*/
public void removeInputChannel(IBinder connectionToken) {
Objects.requireNonNull(connectionToken, "connectionToken must not be null");
- nativeRemoveInputChannel(mPtr, connectionToken);
+ mNative.removeInputChannel(connectionToken);
}
/**
@@ -869,7 +810,7 @@ public class InputManagerService extends IInputManager.Stub
}
}
- nativeSetInputFilterEnabled(mPtr, filter != null);
+ mNative.setInputFilterEnabled(filter != null);
}
}
@@ -893,11 +834,19 @@ public class InputManagerService extends IInputManager.Stub
* @return {@code true} if the touch mode was successfully changed, {@code false} otherwise
*/
public boolean setInTouchMode(boolean inTouchMode, int pid, int uid, boolean hasPermission) {
- return nativeSetInTouchMode(mPtr, inTouchMode, pid, uid, hasPermission);
+ return mNative.setInTouchMode(inTouchMode, pid, uid, hasPermission);
}
@Override // Binder call
- public boolean injectInputEvent(InputEvent event, int mode) {
+ public boolean injectInputEvent(InputEvent event, int mode, int targetUid) {
+ if (!checkCallingPermission(android.Manifest.permission.INJECT_EVENTS,
+ "injectInputEvent()", true /*checkInstrumentationSource*/)) {
+ throw new SecurityException(
+ "Injecting input events requires the caller (or the source of the "
+ + "instrumentation, if any) to have the INJECT_EVENTS permission.");
+ }
+ // We are not checking if targetUid matches the callingUid, since having the permission
+ // already means you can inject into any window.
Objects.requireNonNull(event, "event must not be null");
if (mode != InputEventInjectionSync.NONE
&& mode != InputEventInjectionSync.WAIT_FOR_FINISHED
@@ -906,22 +855,41 @@ public class InputManagerService extends IInputManager.Stub
}
final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity();
+ final boolean injectIntoUid = targetUid != Process.INVALID_UID;
final int result;
try {
- result = nativeInjectInputEvent(mPtr, event, pid, uid, mode,
- INJECTION_TIMEOUT_MILLIS, WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT);
+ result = mNative.injectInputEvent(event, injectIntoUid,
+ targetUid, mode, INJECTION_TIMEOUT_MILLIS,
+ WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT);
} finally {
Binder.restoreCallingIdentity(ident);
}
switch (result) {
- case InputEventInjectionResult.PERMISSION_DENIED:
- Slog.w(TAG, "Input event injection from pid " + pid + " permission denied.");
- throw new SecurityException(
- "Injecting to another application requires INJECT_EVENTS permission");
case InputEventInjectionResult.SUCCEEDED:
return true;
+ case InputEventInjectionResult.TARGET_MISMATCH:
+ if (!injectIntoUid) {
+ throw new IllegalStateException("Injection should not result in TARGET_MISMATCH"
+ + " when it is not targeted into to a specific uid.");
+ }
+ // Attempt to inject into a window owned by the instrumentation source of the caller
+ // because it is possible that tests adopt the identity of the shell when launching
+ // activities that they would like to inject into.
+ final ActivityManagerInternal ami =
+ LocalServices.getService(ActivityManagerInternal.class);
+ Objects.requireNonNull(ami, "ActivityManagerInternal should not be null.");
+ final int instrUid = ami.getInstrumentationSourceUid(Binder.getCallingUid());
+ if (instrUid != Process.INVALID_UID && targetUid != instrUid) {
+ Slog.w(TAG, "Targeted input event was not directed at a window owned by uid "
+ + targetUid + ". Attempting to inject into window owned by "
+ + "instrumentation source uid " + instrUid + ".");
+ return injectInputEvent(event, mode, instrUid);
+ }
+ throw new IllegalArgumentException(
+ "Targeted input event injection from pid " + pid
+ + " was not directed at a window owned by uid "
+ + targetUid + ".");
case InputEventInjectionResult.TIMED_OUT:
Slog.w(TAG, "Input event injection from pid " + pid + " timed out.");
return false;
@@ -935,7 +903,7 @@ public class InputManagerService extends IInputManager.Stub
@Override // Binder call
public VerifiedInputEvent verifyInputEvent(InputEvent event) {
Objects.requireNonNull(event, "event must not be null");
- return nativeVerifyInputEvent(mPtr, event);
+ return mNative.verifyInputEvent(event);
}
/**
@@ -958,7 +926,7 @@ public class InputManagerService extends IInputManager.Stub
// Binder call
@Override
public boolean isInputDeviceEnabled(int deviceId) {
- return nativeIsInputDeviceEnabled(mPtr, deviceId);
+ return mNative.isInputDeviceEnabled(deviceId);
}
// Binder call
@@ -968,7 +936,7 @@ public class InputManagerService extends IInputManager.Stub
"enableInputDevice()")) {
throw new SecurityException("Requires DISABLE_INPUT_DEVICE permission");
}
- nativeEnableInputDevice(mPtr, deviceId);
+ mNative.enableInputDevice(deviceId);
}
// Binder call
@@ -978,7 +946,7 @@ public class InputManagerService extends IInputManager.Stub
"disableInputDevice()")) {
throw new SecurityException("Requires DISABLE_INPUT_DEVICE permission");
}
- nativeDisableInputDevice(mPtr, deviceId);
+ mNative.disableInputDevice(deviceId);
}
/**
@@ -1224,7 +1192,7 @@ public class InputManagerService extends IInputManager.Stub
try {
if (mDataStore.setTouchCalibration(inputDeviceDescriptor, surfaceRotation,
calibration)) {
- nativeReloadCalibration(mPtr);
+ mNative.reloadCalibration();
}
} finally {
mDataStore.saveIfNeeded();
@@ -1736,11 +1704,11 @@ public class InputManagerService extends IInputManager.Stub
}
public void setFocusedApplication(int displayId, InputApplicationHandle application) {
- nativeSetFocusedApplication(mPtr, displayId, application);
+ mNative.setFocusedApplication(displayId, application);
}
public void setFocusedDisplay(int displayId) {
- nativeSetFocusedDisplay(mPtr, displayId);
+ mNative.setFocusedDisplay(displayId);
}
/** Clean up input window handles of the given display. */
@@ -1750,22 +1718,22 @@ public class InputManagerService extends IInputManager.Stub
mPointerIconDisplayContext = null;
}
- nativeDisplayRemoved(mPtr, displayId);
+ mNative.displayRemoved(displayId);
}
@Override
public void requestPointerCapture(IBinder inputChannelToken, boolean enabled) {
Objects.requireNonNull(inputChannelToken, "event must not be null");
- nativeRequestPointerCapture(mPtr, inputChannelToken, enabled);
+ mNative.requestPointerCapture(inputChannelToken, enabled);
}
public void setInputDispatchMode(boolean enabled, boolean frozen) {
- nativeSetInputDispatchMode(mPtr, enabled, frozen);
+ mNative.setInputDispatchMode(enabled, frozen);
}
public void setSystemUiLightsOut(boolean lightsOut) {
- nativeSetSystemUiLightsOut(mPtr, lightsOut);
+ mNative.setSystemUiLightsOut(lightsOut);
}
/**
@@ -1784,7 +1752,7 @@ public class InputManagerService extends IInputManager.Stub
*/
public boolean transferTouchFocus(@NonNull InputChannel fromChannel,
@NonNull InputChannel toChannel, boolean isDragDrop) {
- return nativeTransferTouchFocus(mPtr, fromChannel.getToken(), toChannel.getToken(),
+ return mNative.transferTouchFocus(fromChannel.getToken(), toChannel.getToken(),
isDragDrop);
}
@@ -1805,7 +1773,7 @@ public class InputManagerService extends IInputManager.Stub
@NonNull IBinder toChannelToken) {
Objects.nonNull(fromChannelToken);
Objects.nonNull(toChannelToken);
- return nativeTransferTouchFocus(mPtr, fromChannelToken, toChannelToken,
+ return mNative.transferTouchFocus(fromChannelToken, toChannelToken,
false /* isDragDrop */);
}
@@ -1831,7 +1799,7 @@ public class InputManagerService extends IInputManager.Stub
private void setPointerSpeedUnchecked(int speed) {
speed = Math.min(Math.max(speed, InputManager.MIN_POINTER_SPEED),
InputManager.MAX_POINTER_SPEED);
- nativeSetPointerSpeed(mPtr, speed);
+ mNative.setPointerSpeed(speed);
}
private void setPointerAcceleration(float acceleration, int displayId) {
@@ -1854,7 +1822,7 @@ public class InputManagerService extends IInputManager.Stub
@GuardedBy("mAdditionalDisplayInputPropertiesLock")
private void updatePointerAccelerationLocked(float acceleration) {
- nativeSetPointerAcceleration(mPtr, acceleration);
+ mNative.setPointerAcceleration(acceleration);
}
private void setPointerIconVisible(boolean visible, int displayId) {
@@ -1879,12 +1847,12 @@ public class InputManagerService extends IInputManager.Stub
private void updatePointerIconVisibleLocked(boolean visible) {
if (visible) {
if (mIconType == PointerIcon.TYPE_CUSTOM) {
- nativeSetCustomPointerIcon(mPtr, mIcon);
+ mNative.setCustomPointerIcon(mIcon);
} else {
- nativeSetPointerIconType(mPtr, mIconType);
+ mNative.setPointerIconType(mIconType);
}
} else {
- nativeSetPointerIconType(mPtr, PointerIcon.TYPE_NULL);
+ mNative.setPointerIconType(PointerIcon.TYPE_NULL);
}
}
@@ -1911,7 +1879,7 @@ public class InputManagerService extends IInputManager.Stub
private void updateShowTouchesFromSettings() {
int setting = getShowTouchesSetting(0);
- nativeSetShowTouches(mPtr, setting != 0);
+ mNative.setShowTouches(setting != 0);
}
private void registerShowTouchesSettingObserver() {
@@ -1930,7 +1898,7 @@ public class InputManagerService extends IInputManager.Stub
mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON,
0, UserHandle.USER_CURRENT);
PointerIcon.setUseLargeIcons(accessibilityConfig == 1);
- nativeReloadPointerIcons(mPtr);
+ mNative.reloadPointerIcons();
}
private void registerAccessibilityLargePointerSettingObserver() {
@@ -1958,7 +1926,7 @@ public class InputManagerService extends IInputManager.Stub
(enabled ? "Enabling" : "Disabling") + " motion classifier because " + reason
+ ": feature " + (featureEnabledFlag ? "enabled" : "disabled")
+ ", long press timeout = " + timeout);
- nativeSetMotionClassifierEnabled(mPtr, enabled);
+ mNative.setMotionClassifierEnabled(enabled);
}
private void registerLongPressTimeoutObserver() {
@@ -1986,7 +1954,7 @@ public class InputManagerService extends IInputManager.Stub
private void updateBlockUntrustedTouchesModeFromSettings() {
final int mode = InputManager.getInstance().getBlockUntrustedTouchesMode(mContext);
- nativeSetBlockUntrustedTouchesMode(mPtr, mode);
+ mNative.setBlockUntrustedTouchesMode(mode);
}
private void registerMaximumObscuringOpacityForTouchSettingObserver() {
@@ -2008,7 +1976,7 @@ public class InputManagerService extends IInputManager.Stub
+ ", it should be >= 0 and <= 1, rejecting update.");
return;
}
- nativeSetMaximumObscuringOpacityForTouch(mPtr, opacity);
+ mNative.setMaximumObscuringOpacityForTouch(opacity);
}
private int getShowTouchesSetting(int defaultValue) {
@@ -2034,7 +2002,7 @@ public class InputManagerService extends IInputManager.Stub
}
}
// TODO(b/215597605): trigger MousePositionTracker update
- nativeNotifyPointerDisplayIdChanged(mPtr);
+ mNative.notifyPointerDisplayIdChanged();
}
private int getVirtualMousePointerDisplayId() {
@@ -2044,7 +2012,7 @@ public class InputManagerService extends IInputManager.Stub
}
private void setDisplayEligibilityForPointerCapture(int displayId, boolean isEligible) {
- nativeSetDisplayEligibilityForPointerCapture(mPtr, displayId, isEligible);
+ mNative.setDisplayEligibilityForPointerCapture(displayId, isEligible);
}
private static class VibrationInfo {
@@ -2143,7 +2111,7 @@ public class InputManagerService extends IInputManager.Stub
VibratorToken v = getVibratorToken(deviceId, token);
synchronized (v) {
v.mVibrating = true;
- nativeVibrate(mPtr, deviceId, info.getPattern(), info.getAmplitudes(),
+ mNative.vibrate(deviceId, info.getPattern(), info.getAmplitudes(),
info.getRepeatIndex(), v.mTokenValue);
}
}
@@ -2151,13 +2119,13 @@ public class InputManagerService extends IInputManager.Stub
// Binder call
@Override
public int[] getVibratorIds(int deviceId) {
- return nativeGetVibratorIds(mPtr, deviceId);
+ return mNative.getVibratorIds(deviceId);
}
// Binder call
@Override
public boolean isVibrating(int deviceId) {
- return nativeIsVibrating(mPtr, deviceId);
+ return mNative.isVibrating(deviceId);
}
// Binder call
@@ -2175,7 +2143,7 @@ public class InputManagerService extends IInputManager.Stub
if (effect instanceof CombinedVibration.Mono) {
CombinedVibration.Mono mono = (CombinedVibration.Mono) effect;
VibrationInfo info = new VibrationInfo(mono.getEffect());
- nativeVibrate(mPtr, deviceId, info.getPattern(), info.getAmplitudes(),
+ mNative.vibrate(deviceId, info.getPattern(), info.getAmplitudes(),
info.getRepeatIndex(), v.mTokenValue);
} else if (effect instanceof CombinedVibration.Stereo) {
CombinedVibration.Stereo stereo = (CombinedVibration.Stereo) effect;
@@ -2194,7 +2162,7 @@ public class InputManagerService extends IInputManager.Stub
}
amplitudes.put(effects.keyAt(i), info.getAmplitudes());
}
- nativeVibrateCombined(mPtr, deviceId, pattern, amplitudes, repeat,
+ mNative.vibrateCombined(deviceId, pattern, amplitudes, repeat,
v.mTokenValue);
}
}
@@ -2225,7 +2193,7 @@ public class InputManagerService extends IInputManager.Stub
private void cancelVibrateIfNeeded(VibratorToken v) {
synchronized (v) {
if (v.mVibrating) {
- nativeCancelVibrate(mPtr, v.mDeviceId, v.mTokenValue);
+ mNative.cancelVibrate(v.mDeviceId, v.mTokenValue);
v.mVibrating = false;
}
}
@@ -2321,13 +2289,13 @@ public class InputManagerService extends IInputManager.Stub
// Binder call
@Override
public int getBatteryStatus(int deviceId) {
- return nativeGetBatteryStatus(mPtr, deviceId);
+ return mNative.getBatteryStatus(deviceId);
}
// Binder call
@Override
public int getBatteryCapacity(int deviceId) {
- return nativeGetBatteryCapacity(mPtr, deviceId);
+ return mNative.getBatteryCapacity(deviceId);
}
// Binder call
@@ -2343,10 +2311,10 @@ public class InputManagerService extends IInputManager.Stub
final AdditionalDisplayInputProperties properties =
mAdditionalDisplayInputProperties.get(mOverriddenPointerDisplayId);
if (properties == null || properties.pointerIconVisible) {
- nativeSetPointerIconType(mPtr, mIconType);
+ mNative.setPointerIconType(mIconType);
}
} else {
- nativeSetPointerIconType(mPtr, mIconType);
+ mNative.setPointerIconType(mIconType);
}
}
}
@@ -2364,10 +2332,10 @@ public class InputManagerService extends IInputManager.Stub
if (properties == null || properties.pointerIconVisible) {
// Only set the icon if it is not currently hidden; otherwise, it will be set
// once it's no longer hidden.
- nativeSetCustomPointerIcon(mPtr, mIcon);
+ mNative.setCustomPointerIcon(mIcon);
}
} else {
- nativeSetCustomPointerIcon(mPtr, mIcon);
+ mNative.setCustomPointerIcon(mIcon);
}
}
}
@@ -2391,7 +2359,7 @@ public class InputManagerService extends IInputManager.Stub
synchronized (mAssociationsLock) {
mRuntimeAssociations.put(inputPort, displayPort);
}
- nativeNotifyPortAssociationsChanged(mPtr);
+ mNative.notifyPortAssociationsChanged();
}
/**
@@ -2412,7 +2380,7 @@ public class InputManagerService extends IInputManager.Stub
synchronized (mAssociationsLock) {
mRuntimeAssociations.remove(inputPort);
}
- nativeNotifyPortAssociationsChanged(mPtr);
+ mNative.notifyPortAssociationsChanged();
}
@Override // Binder call
@@ -2429,7 +2397,7 @@ public class InputManagerService extends IInputManager.Stub
synchronized (mAssociationsLock) {
mUniqueIdAssociations.put(inputPort, displayUniqueId);
}
- nativeChangeUniqueIdAssociation(mPtr);
+ mNative.changeUniqueIdAssociation();
}
@Override // Binder call
@@ -2445,12 +2413,12 @@ public class InputManagerService extends IInputManager.Stub
synchronized (mAssociationsLock) {
mUniqueIdAssociations.remove(inputPort);
}
- nativeChangeUniqueIdAssociation(mPtr);
+ mNative.changeUniqueIdAssociation();
}
@Override // Binder call
public InputSensorInfo[] getSensorList(int deviceId) {
- return nativeGetSensorList(mPtr, deviceId);
+ return mNative.getSensorList(deviceId);
}
@Override // Binder call
@@ -2511,7 +2479,7 @@ public class InputManagerService extends IInputManager.Stub
int callingPid = Binder.getCallingPid();
SensorEventListenerRecord listener = mSensorEventListeners.get(callingPid);
if (listener != null) {
- return nativeFlushSensor(mPtr, deviceId, sensorType);
+ return mNative.flushSensor(deviceId, sensorType);
}
return false;
}
@@ -2521,7 +2489,7 @@ public class InputManagerService extends IInputManager.Stub
public boolean enableSensor(int deviceId, int sensorType, int samplingPeriodUs,
int maxBatchReportLatencyUs) {
synchronized (mInputDevicesLock) {
- return nativeEnableSensor(mPtr, deviceId, sensorType, samplingPeriodUs,
+ return mNative.enableSensor(deviceId, sensorType, samplingPeriodUs,
maxBatchReportLatencyUs);
}
}
@@ -2529,7 +2497,7 @@ public class InputManagerService extends IInputManager.Stub
@Override // Binder call
public void disableSensor(int deviceId, int sensorType) {
synchronized (mInputDevicesLock) {
- nativeDisableSensor(mPtr, deviceId, sensorType);
+ mNative.disableSensor(deviceId, sensorType);
}
}
@@ -2568,7 +2536,7 @@ public class InputManagerService extends IInputManager.Stub
*/
@Override // Binder call
public List<Light> getLights(int deviceId) {
- return nativeGetLights(mPtr, deviceId);
+ return mNative.getLights(deviceId);
}
/**
@@ -2581,11 +2549,11 @@ public class InputManagerService extends IInputManager.Stub
+ "lightState " + lightState);
}
if (light.getType() == Light.LIGHT_TYPE_PLAYER_ID) {
- nativeSetLightPlayerId(mPtr, deviceId, light.getId(), lightState.getPlayerId());
+ mNative.setLightPlayerId(deviceId, light.getId(), lightState.getPlayerId());
} else {
// Set ARGB format color to input device light
// Refer to https://developer.android.com/reference/kotlin/android/graphics/Color
- nativeSetLightColor(mPtr, deviceId, light.getId(), lightState.getColor());
+ mNative.setLightColor(deviceId, light.getId(), lightState.getColor());
}
}
@@ -2593,7 +2561,7 @@ public class InputManagerService extends IInputManager.Stub
* Set multiple light states with multiple light ids for a specific input device.
*/
private void setLightStatesInternal(int deviceId, int[] lightIds, LightState[] lightStates) {
- final List<Light> lights = nativeGetLights(mPtr, deviceId);
+ final List<Light> lights = mNative.getLights(deviceId);
SparseArray<Light> lightArray = new SparseArray<>();
for (int i = 0; i < lights.size(); i++) {
lightArray.put(lights.get(i).getId(), lights.get(i));
@@ -2629,8 +2597,8 @@ public class InputManagerService extends IInputManager.Stub
@Override
public @Nullable LightState getLightState(int deviceId, int lightId) {
synchronized (mLightLock) {
- int color = nativeGetLightColor(mPtr, deviceId, lightId);
- int playerId = nativeGetLightPlayerId(mPtr, deviceId, lightId);
+ int color = mNative.getLightColor(deviceId, lightId);
+ int playerId = mNative.getLightPlayerId(deviceId, lightId);
return new LightState(color, playerId);
}
@@ -2682,7 +2650,7 @@ public class InputManagerService extends IInputManager.Stub
throw new SecurityException("Requires MONITOR_INPUT permission");
}
- nativeCancelCurrentTouch(mPtr);
+ mNative.cancelCurrentTouch();
}
@Override
@@ -2690,7 +2658,7 @@ public class InputManagerService extends IInputManager.Stub
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
pw.println("INPUT MANAGER (dumpsys input)\n");
- String dumpStr = nativeDump(mPtr);
+ String dumpStr = mNative.dump();
if (dumpStr != null) {
pw.println(dumpStr);
}
@@ -2757,8 +2725,12 @@ public class InputManagerService extends IInputManager.Stub
}
}
}
-
private boolean checkCallingPermission(String permission, String func) {
+ return checkCallingPermission(permission, func, false /*checkInstrumentationSource*/);
+ }
+
+ private boolean checkCallingPermission(String permission, String func,
+ boolean checkInstrumentationSource) {
// Quick check: if the calling permission is me, it's all okay.
if (Binder.getCallingPid() == Process.myPid()) {
return true;
@@ -2767,6 +2739,18 @@ public class InputManagerService extends IInputManager.Stub
if (mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED) {
return true;
}
+
+ if (checkInstrumentationSource) {
+ final ActivityManagerInternal ami =
+ LocalServices.getService(ActivityManagerInternal.class);
+ Objects.requireNonNull(ami, "ActivityManagerInternal should not be null.");
+ final int instrumentationUid = ami.getInstrumentationSourceUid(Binder.getCallingUid());
+ if (instrumentationUid != Process.INVALID_UID && mContext.checkPermission(permission,
+ -1 /*pid*/, instrumentationUid) == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+ }
+
String msg = "Permission Denial: " + func + " from pid="
+ Binder.getCallingPid()
+ ", uid=" + Binder.getCallingUid()
@@ -2783,7 +2767,7 @@ public class InputManagerService extends IInputManager.Stub
synchronized (mLidSwitchLock) { /* Test if blocked by lid switch lock. */ }
synchronized (mInputMonitors) { /* Test if blocked by input monitor lock. */ }
synchronized (mAdditionalDisplayInputPropertiesLock) { /* Test if blocked by props lock */ }
- nativeMonitor(mPtr);
+ mNative.monitor();
}
// Native callback.
@@ -3012,13 +2996,6 @@ public class InputManagerService extends IInputManager.Stub
// Native callback.
@SuppressWarnings("unused")
- private boolean checkInjectEventsPermission(int injectorPid, int injectorUid) {
- return mContext.checkPermission(android.Manifest.permission.INJECT_EVENTS,
- injectorPid, injectorUid) == PackageManager.PERMISSION_GRANTED;
- }
-
- // Native callback.
- @SuppressWarnings("unused")
private void onPointerDownOutsideFocus(IBinder touchedToken) {
mWindowManagerCallbacks.onPointerDownOutsideFocus(touchedToken);
}
@@ -3117,7 +3094,7 @@ public class InputManagerService extends IInputManager.Stub
* @return True if the device could dispatch to the given display, false otherwise.
*/
public boolean canDispatchToDisplay(int deviceId, int displayId) {
- return nativeCanDispatchToDisplay(mPtr, deviceId, displayId);
+ return mNative.canDispatchToDisplay(deviceId, displayId);
}
// Native callback.
@@ -3459,12 +3436,17 @@ public class InputManagerService extends IInputManager.Stub
@Override
public void sendInputEvent(InputEvent event, int policyFlags) {
+ if (!checkCallingPermission(android.Manifest.permission.INJECT_EVENTS,
+ "sendInputEvent()")) {
+ throw new SecurityException(
+ "The INJECT_EVENTS permission is required for injecting input events.");
+ }
Objects.requireNonNull(event, "event must not be null");
synchronized (mInputFilterLock) {
if (!mDisconnected) {
- nativeInjectInputEvent(mPtr, event, 0, 0,
- InputManager.INJECT_INPUT_EVENT_MODE_ASYNC, 0,
+ mNative.injectInputEvent(event, false /* injectIntoUid */, -1 /* uid */,
+ InputManager.INJECT_INPUT_EVENT_MODE_ASYNC, 0 /* timeout */,
policyFlags | WindowManagerPolicy.FLAG_FILTERED);
}
}
@@ -3483,7 +3465,7 @@ public class InputManagerService extends IInputManager.Stub
@Override
public void pilferPointers() {
- nativePilferPointers(mPtr, mInputChannelToken);
+ mNative.pilferPointers(mInputChannelToken);
}
@Override
@@ -3661,12 +3643,12 @@ public class InputManagerService extends IInputManager.Stub
@Override
public void setInteractive(boolean interactive) {
- nativeSetInteractive(mPtr, interactive);
+ mNative.setInteractive(interactive);
}
@Override
public void toggleCapsLock(int deviceId) {
- nativeToggleCapsLock(mPtr, deviceId);
+ mNative.toggleCapsLock(deviceId);
}
@Override
@@ -3737,7 +3719,7 @@ public class InputManagerService extends IInputManager.Stub
@Override
public void pilferPointers(IBinder token) {
- nativePilferPointers(mPtr, token);
+ mNative.pilferPointers(token);
}
}
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
new file mode 100644
index 000000000000..7178d20786e3
--- /dev/null
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -0,0 +1,389 @@
+/*
+ * 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.server.input;
+
+import android.content.Context;
+import android.hardware.display.DisplayViewport;
+import android.hardware.input.InputSensorInfo;
+import android.hardware.lights.Light;
+import android.os.IBinder;
+import android.os.MessageQueue;
+import android.util.SparseArray;
+import android.view.InputApplicationHandle;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.PointerIcon;
+import android.view.VerifiedInputEvent;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.List;
+
+/**
+ * An interface for the native methods of InputManagerService. We use a public interface so that
+ * this can be mocked for testing by Mockito.
+ */
+@VisibleForTesting
+public interface NativeInputManagerService {
+
+ void start();
+
+ void setDisplayViewports(DisplayViewport[] viewports);
+
+ int getScanCodeState(int deviceId, int sourceMask, int scanCode);
+
+ int getKeyCodeState(int deviceId, int sourceMask, int keyCode);
+
+ int getSwitchState(int deviceId, int sourceMask, int sw);
+
+ boolean hasKeys(int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists);
+
+ int getKeyCodeForKeyLocation(int deviceId, int locationKeyCode);
+
+ InputChannel createInputChannel(String name);
+
+ InputChannel createInputMonitor(int displayId, String name, int pid);
+
+ void removeInputChannel(IBinder connectionToken);
+
+ void pilferPointers(IBinder token);
+
+ void setInputFilterEnabled(boolean enable);
+
+ boolean setInTouchMode(boolean inTouchMode, int pid, int uid, boolean hasPermission);
+
+ void setMaximumObscuringOpacityForTouch(float opacity);
+
+ void setBlockUntrustedTouchesMode(int mode);
+
+ int injectInputEvent(InputEvent event, boolean injectIntoUid, int uid, int syncMode,
+ int timeoutMillis, int policyFlags);
+
+ VerifiedInputEvent verifyInputEvent(InputEvent event);
+
+ void toggleCapsLock(int deviceId);
+
+ void displayRemoved(int displayId);
+
+ void setInputDispatchMode(boolean enabled, boolean frozen);
+
+ void setSystemUiLightsOut(boolean lightsOut);
+
+ void setFocusedApplication(int displayId, InputApplicationHandle application);
+
+ void setFocusedDisplay(int displayId);
+
+ boolean transferTouchFocus(IBinder fromChannelToken, IBinder toChannelToken,
+ boolean isDragDrop);
+
+ boolean transferTouch(IBinder destChannelToken);
+
+ void setPointerSpeed(int speed);
+
+ void setPointerAcceleration(float acceleration);
+
+ void setShowTouches(boolean enabled);
+
+ void setInteractive(boolean interactive);
+
+ void reloadCalibration();
+
+ void vibrate(int deviceId, long[] pattern, int[] amplitudes, int repeat, int token);
+
+ void vibrateCombined(int deviceId, long[] pattern, SparseArray<int[]> amplitudes,
+ int repeat, int token);
+
+ void cancelVibrate(int deviceId, int token);
+
+ boolean isVibrating(int deviceId);
+
+ int[] getVibratorIds(int deviceId);
+
+ int getBatteryCapacity(int deviceId);
+
+ int getBatteryStatus(int deviceId);
+
+ List<Light> getLights(int deviceId);
+
+ int getLightPlayerId(int deviceId, int lightId);
+
+ int getLightColor(int deviceId, int lightId);
+
+ void setLightPlayerId(int deviceId, int lightId, int playerId);
+
+ void setLightColor(int deviceId, int lightId, int color);
+
+ void reloadKeyboardLayouts();
+
+ void reloadDeviceAliases();
+
+ String dump();
+
+ void monitor();
+
+ boolean isInputDeviceEnabled(int deviceId);
+
+ void enableInputDevice(int deviceId);
+
+ void disableInputDevice(int deviceId);
+
+ void setPointerIconType(int iconId);
+
+ void reloadPointerIcons();
+
+ void setCustomPointerIcon(PointerIcon icon);
+
+ void requestPointerCapture(IBinder windowToken, boolean enabled);
+
+ boolean canDispatchToDisplay(int deviceId, int displayId);
+
+ void notifyPortAssociationsChanged();
+
+ void changeUniqueIdAssociation();
+
+ void notifyPointerDisplayIdChanged();
+
+ void setDisplayEligibilityForPointerCapture(int displayId, boolean enabled);
+
+ void setMotionClassifierEnabled(boolean enabled);
+
+ InputSensorInfo[] getSensorList(int deviceId);
+
+ boolean flushSensor(int deviceId, int sensorType);
+
+ boolean enableSensor(int deviceId, int sensorType, int samplingPeriodUs,
+ int maxBatchReportLatencyUs);
+
+ void disableSensor(int deviceId, int sensorType);
+
+ void cancelCurrentTouch();
+
+ /** The native implementation of InputManagerService methods. */
+ class NativeImpl implements NativeInputManagerService {
+ /** Pointer to native input manager service object, used by native code. */
+ @SuppressWarnings({"unused", "FieldCanBeLocal"})
+ private final long mPtr;
+
+ NativeImpl(InputManagerService service, Context context, MessageQueue messageQueue) {
+ mPtr = init(service, context, messageQueue);
+ }
+
+ private native long init(InputManagerService service, Context context,
+ MessageQueue messageQueue);
+
+ @Override
+ public native void start();
+
+ @Override
+ public native void setDisplayViewports(DisplayViewport[] viewports);
+
+ @Override
+ public native int getScanCodeState(int deviceId, int sourceMask, int scanCode);
+
+ @Override
+ public native int getKeyCodeState(int deviceId, int sourceMask, int keyCode);
+
+ @Override
+ public native int getSwitchState(int deviceId, int sourceMask, int sw);
+
+ @Override
+ public native boolean hasKeys(int deviceId, int sourceMask, int[] keyCodes,
+ boolean[] keyExists);
+
+ @Override
+ public native int getKeyCodeForKeyLocation(int deviceId, int locationKeyCode);
+
+ @Override
+ public native InputChannel createInputChannel(String name);
+
+ @Override
+ public native InputChannel createInputMonitor(int displayId, String name, int pid);
+
+ @Override
+ public native void removeInputChannel(IBinder connectionToken);
+
+ @Override
+ public native void pilferPointers(IBinder token);
+
+ @Override
+ public native void setInputFilterEnabled(boolean enable);
+
+ @Override
+ public native boolean setInTouchMode(boolean inTouchMode, int pid, int uid,
+ boolean hasPermission);
+
+ @Override
+ public native void setMaximumObscuringOpacityForTouch(float opacity);
+
+ @Override
+ public native void setBlockUntrustedTouchesMode(int mode);
+
+ @Override
+ public native int injectInputEvent(InputEvent event, boolean injectIntoUid, int uid,
+ int syncMode,
+ int timeoutMillis, int policyFlags);
+
+ @Override
+ public native VerifiedInputEvent verifyInputEvent(InputEvent event);
+
+ @Override
+ public native void toggleCapsLock(int deviceId);
+
+ @Override
+ public native void displayRemoved(int displayId);
+
+ @Override
+ public native void setInputDispatchMode(boolean enabled, boolean frozen);
+
+ @Override
+ public native void setSystemUiLightsOut(boolean lightsOut);
+
+ @Override
+ public native void setFocusedApplication(int displayId, InputApplicationHandle application);
+
+ @Override
+ public native void setFocusedDisplay(int displayId);
+
+ @Override
+ public native boolean transferTouchFocus(IBinder fromChannelToken, IBinder toChannelToken,
+ boolean isDragDrop);
+
+ @Override
+ public native boolean transferTouch(IBinder destChannelToken);
+
+ @Override
+ public native void setPointerSpeed(int speed);
+
+ @Override
+ public native void setPointerAcceleration(float acceleration);
+
+ @Override
+ public native void setShowTouches(boolean enabled);
+
+ @Override
+ public native void setInteractive(boolean interactive);
+
+ @Override
+ public native void reloadCalibration();
+
+ @Override
+ public native void vibrate(int deviceId, long[] pattern, int[] amplitudes, int repeat,
+ int token);
+
+ @Override
+ public native void vibrateCombined(int deviceId, long[] pattern,
+ SparseArray<int[]> amplitudes,
+ int repeat, int token);
+
+ @Override
+ public native void cancelVibrate(int deviceId, int token);
+
+ @Override
+ public native boolean isVibrating(int deviceId);
+
+ @Override
+ public native int[] getVibratorIds(int deviceId);
+
+ @Override
+ public native int getBatteryCapacity(int deviceId);
+
+ @Override
+ public native int getBatteryStatus(int deviceId);
+
+ @Override
+ public native List<Light> getLights(int deviceId);
+
+ @Override
+ public native int getLightPlayerId(int deviceId, int lightId);
+
+ @Override
+ public native int getLightColor(int deviceId, int lightId);
+
+ @Override
+ public native void setLightPlayerId(int deviceId, int lightId, int playerId);
+
+ @Override
+ public native void setLightColor(int deviceId, int lightId, int color);
+
+ @Override
+ public native void reloadKeyboardLayouts();
+
+ @Override
+ public native void reloadDeviceAliases();
+
+ @Override
+ public native String dump();
+
+ @Override
+ public native void monitor();
+
+ @Override
+ public native boolean isInputDeviceEnabled(int deviceId);
+
+ @Override
+ public native void enableInputDevice(int deviceId);
+
+ @Override
+ public native void disableInputDevice(int deviceId);
+
+ @Override
+ public native void setPointerIconType(int iconId);
+
+ @Override
+ public native void reloadPointerIcons();
+
+ @Override
+ public native void setCustomPointerIcon(PointerIcon icon);
+
+ @Override
+ public native void requestPointerCapture(IBinder windowToken, boolean enabled);
+
+ @Override
+ public native boolean canDispatchToDisplay(int deviceId, int displayId);
+
+ @Override
+ public native void notifyPortAssociationsChanged();
+
+ @Override
+ public native void changeUniqueIdAssociation();
+
+ @Override
+ public native void notifyPointerDisplayIdChanged();
+
+ @Override
+ public native void setDisplayEligibilityForPointerCapture(int displayId, boolean enabled);
+
+ @Override
+ public native void setMotionClassifierEnabled(boolean enabled);
+
+ @Override
+ public native InputSensorInfo[] getSensorList(int deviceId);
+
+ @Override
+ public native boolean flushSensor(int deviceId, int sensorType);
+
+ @Override
+ public native boolean enableSensor(int deviceId, int sensorType, int samplingPeriodUs,
+ int maxBatchReportLatencyUs);
+
+ @Override
+ public native void disableSensor(int deviceId, int sensorType);
+
+ @Override
+ public native void cancelCurrentTouch();
+ }
+}
diff --git a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
index db81393a9ad6..37a486923b12 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
@@ -30,8 +30,6 @@ import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
-import android.os.Binder;
import android.os.HandlerThread;
import android.os.LocaleList;
import android.os.RemoteException;
@@ -78,7 +76,7 @@ class LocaleManagerBackupHelper {
private static final Duration STAGE_DATA_RETENTION_PERIOD = Duration.ofDays(3);
private final LocaleManagerService mLocaleManagerService;
- private final PackageManagerInternal mPackageManagerInternal;
+ private final PackageManager mPackageManager;
private final Clock mClock;
private final Context mContext;
private final Object mStagedDataLock = new Object();
@@ -90,18 +88,18 @@ class LocaleManagerBackupHelper {
private final BroadcastReceiver mUserMonitor;
LocaleManagerBackupHelper(LocaleManagerService localeManagerService,
- PackageManagerInternal pmInternal, HandlerThread broadcastHandlerThread) {
- this(localeManagerService.mContext, localeManagerService, pmInternal, Clock.systemUTC(),
+ PackageManager packageManager, HandlerThread broadcastHandlerThread) {
+ this(localeManagerService.mContext, localeManagerService, packageManager, Clock.systemUTC(),
new SparseArray<>(), broadcastHandlerThread);
}
@VisibleForTesting LocaleManagerBackupHelper(Context context,
LocaleManagerService localeManagerService,
- PackageManagerInternal pmInternal, Clock clock, SparseArray<StagedData> stagedData,
+ PackageManager packageManager, Clock clock, SparseArray<StagedData> stagedData,
HandlerThread broadcastHandlerThread) {
mContext = context;
mLocaleManagerService = localeManagerService;
- mPackageManagerInternal = pmInternal;
+ mPackageManager = packageManager;
mClock = clock;
mStagedData = stagedData;
@@ -130,8 +128,8 @@ class LocaleManagerBackupHelper {
}
HashMap<String, String> pkgStates = new HashMap<>();
- for (ApplicationInfo appInfo : mPackageManagerInternal.getInstalledApplications(/*flags*/0,
- userId, Binder.getCallingUid())) {
+ for (ApplicationInfo appInfo : mPackageManager.getInstalledApplicationsAsUser(
+ PackageManager.ApplicationInfoFlags.of(0), userId)) {
try {
LocaleList appLocales = mLocaleManagerService.getApplicationLocales(
appInfo.packageName,
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index 924db6a49eeb..fc7be7ff8d1c 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -28,7 +28,7 @@ import android.app.ILocaleManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageManager.PackageInfoFlags;
import android.content.res.Configuration;
import android.os.Binder;
import android.os.HandlerThread;
@@ -60,7 +60,7 @@ public class LocaleManagerService extends SystemService {
private final LocaleManagerService.LocaleManagerBinderService mBinderService;
private ActivityTaskManagerInternal mActivityTaskManagerInternal;
private ActivityManagerInternal mActivityManagerInternal;
- private PackageManagerInternal mPackageManagerInternal;
+ private PackageManager mPackageManager;
private LocaleManagerBackupHelper mBackupHelper;
@@ -74,7 +74,7 @@ public class LocaleManagerService extends SystemService {
mBinderService = new LocaleManagerBinderService();
mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
- mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+ mPackageManager = mContext.getPackageManager();
HandlerThread broadcastHandlerThread = new HandlerThread(TAG,
Process.THREAD_PRIORITY_BACKGROUND);
@@ -90,7 +90,7 @@ public class LocaleManagerService extends SystemService {
});
mBackupHelper = new LocaleManagerBackupHelper(this,
- mPackageManagerInternal, broadcastHandlerThread);
+ mPackageManager, broadcastHandlerThread);
mPackageMonitor = new LocaleManagerServicePackageMonitor(mBackupHelper,
systemAppUpdateTracker);
@@ -102,7 +102,7 @@ public class LocaleManagerService extends SystemService {
@VisibleForTesting
LocaleManagerService(Context context, ActivityTaskManagerInternal activityTaskManagerInternal,
ActivityManagerInternal activityManagerInternal,
- PackageManagerInternal packageManagerInternal,
+ PackageManager packageManager,
LocaleManagerBackupHelper localeManagerBackupHelper,
PackageMonitor packageMonitor) {
super(context);
@@ -110,7 +110,7 @@ public class LocaleManagerService extends SystemService {
mBinderService = new LocaleManagerBinderService();
mActivityTaskManagerInternal = activityTaskManagerInternal;
mActivityManagerInternal = activityManagerInternal;
- mPackageManagerInternal = packageManagerInternal;
+ mPackageManager = packageManager;
mBackupHelper = localeManagerBackupHelper;
mPackageMonitor = packageMonitor;
}
@@ -186,7 +186,7 @@ public class LocaleManagerService extends SystemService {
userId = mActivityManagerInternal.handleIncomingUser(
Binder.getCallingPid(), Binder.getCallingUid(), userId,
false /* allowAll */, ActivityManagerInternal.ALLOW_NON_FULL,
- "setApplicationLocales", appPackageName);
+ "setApplicationLocales", /* callerPackage= */ null);
// This function handles two types of set operations:
// 1.) A normal, non-privileged app setting its own locale.
@@ -355,7 +355,7 @@ public class LocaleManagerService extends SystemService {
userId = mActivityManagerInternal.handleIncomingUser(
Binder.getCallingPid(), Binder.getCallingUid(), userId,
false /* allowAll */, ActivityManagerInternal.ALLOW_NON_FULL,
- "getApplicationLocales", appPackageName);
+ "getApplicationLocales", /* callerPackage= */ null);
// This function handles three types of query operations:
// 1.) A normal, non-privileged app querying its own locale.
@@ -419,8 +419,12 @@ public class LocaleManagerService extends SystemService {
}
private int getPackageUid(String appPackageName, int userId) {
- return mPackageManagerInternal
- .getPackageUid(appPackageName, /* flags */ 0, userId);
+ try {
+ return mPackageManager
+ .getPackageUidAsUser(appPackageName, PackageInfoFlags.of(0), userId);
+ } catch (PackageManager.NameNotFoundException e) {
+ return Process.INVALID_UID;
+ }
}
@Nullable
diff --git a/services/core/java/com/android/server/locales/OWNERS b/services/core/java/com/android/server/locales/OWNERS
index be284a766c50..4d93bff6f7d3 100644
--- a/services/core/java/com/android/server/locales/OWNERS
+++ b/services/core/java/com/android/server/locales/OWNERS
@@ -1,3 +1,4 @@
roosa@google.com
pratyushmore@google.com
goldmanj@google.com
+ankitavyas@google.com
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index fac510651878..31d5136c80a5 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -94,7 +94,6 @@ import android.util.IndentingPrintWriter;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.server.FgThread;
@@ -279,6 +278,9 @@ public class LocationManagerService extends ILocationManager.Stub implements
this::onLocationUserSettingsChanged);
mInjector.getSettingsHelper().addOnLocationEnabledChangedListener(
this::onLocationModeChanged);
+ mInjector.getSettingsHelper().addAdasAllowlistChangedListener(
+ () -> refreshAppOpsRestrictions(UserHandle.USER_ALL)
+ );
mInjector.getSettingsHelper().addIgnoreSettingsAllowlistChangedListener(
() -> refreshAppOpsRestrictions(UserHandle.USER_ALL));
mInjector.getUserInfoHelper().addListener((userId, change) -> {
@@ -823,12 +825,6 @@ public class LocationManagerService extends ILocationManager.Stub implements
throw new IllegalArgumentException(
"adas gnss bypass requests are only allowed on the \"gps\" provider");
}
- if (!ArrayUtils.contains(mContext.getResources().getStringArray(
- com.android.internal.R.array.config_locationDriverAssistancePackageNames),
- identity.getPackageName())) {
- throw new SecurityException(
- "only verified adas packages may use adas gnss bypass requests");
- }
if (!isLocationProvider) {
LocationPermissions.enforceCallingOrSelfBypassPermission(mContext);
}
@@ -923,12 +919,6 @@ public class LocationManagerService extends ILocationManager.Stub implements
throw new IllegalArgumentException(
"adas gnss bypass requests are only allowed on the \"gps\" provider");
}
- if (!ArrayUtils.contains(mContext.getResources().getStringArray(
- com.android.internal.R.array.config_locationDriverAssistancePackageNames),
- identity.getPackageName())) {
- throw new SecurityException(
- "only verified adas packages may use adas gnss bypass requests");
- }
if (!isLocationProvider) {
LocationPermissions.enforceCallingOrSelfBypassPermission(mContext);
}
@@ -1542,6 +1532,7 @@ public class LocationManagerService extends ILocationManager.Stub implements
}
}
builder.add(mInjector.getSettingsHelper().getIgnoreSettingsAllowlist());
+ builder.add(mInjector.getSettingsHelper().getAdasAllowlist());
allowedPackages = builder.build();
}
diff --git a/services/core/java/com/android/server/location/injector/SettingsHelper.java b/services/core/java/com/android/server/location/injector/SettingsHelper.java
index 148afa75ca75..490bfe1ab82f 100644
--- a/services/core/java/com/android/server/location/injector/SettingsHelper.java
+++ b/services/core/java/com/android/server/location/injector/SettingsHelper.java
@@ -146,6 +146,20 @@ public abstract class SettingsHelper {
public abstract void removeOnGnssMeasurementsFullTrackingEnabledChangedListener(
GlobalSettingChangedListener listener);
+ /** Retrieve adas allowlist. */
+ public abstract PackageTagsList getAdasAllowlist();
+
+ /**
+ * Add a listener for changes to the ADAS settings package allowlist. Callbacks occur on an
+ * unspecified thread.
+ */
+ public abstract void addAdasAllowlistChangedListener(GlobalSettingChangedListener listener);
+
+ /**
+ * Remove a listener for changes to the ADAS package allowlist.
+ */
+ public abstract void removeAdasAllowlistChangedListener(GlobalSettingChangedListener listener);
+
/**
* Retrieve the ignore location settings package+tags allowlist setting.
*/
diff --git a/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java b/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java
index 3e8da7d7478a..777683ef59cf 100644
--- a/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java
@@ -16,6 +16,7 @@
package com.android.server.location.injector;
+import static android.location.LocationDeviceConfig.ADAS_SETTINGS_ALLOWLIST;
import static android.location.LocationDeviceConfig.IGNORE_SETTINGS_ALLOWLIST;
import static android.provider.Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING;
import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS;
@@ -80,6 +81,7 @@ public class SystemSettingsHelper extends SettingsHelper {
private final StringListCachedSecureSetting mLocationPackageBlacklist;
private final StringListCachedSecureSetting mLocationPackageWhitelist;
private final StringSetCachedGlobalSetting mBackgroundThrottlePackageWhitelist;
+ private final PackageTagsListSetting mAdasPackageAllowlist;
private final PackageTagsListSetting mIgnoreSettingsPackageAllowlist;
public SystemSettingsHelper(Context context) {
@@ -98,6 +100,9 @@ public class SystemSettingsHelper extends SettingsHelper {
LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST,
() -> SystemConfig.getInstance().getAllowUnthrottledLocation(),
FgThread.getHandler());
+ mAdasPackageAllowlist = new PackageTagsListSetting(
+ ADAS_SETTINGS_ALLOWLIST,
+ () -> SystemConfig.getInstance().getAllowAdasLocationSettings());
mIgnoreSettingsPackageAllowlist = new PackageTagsListSetting(
IGNORE_SETTINGS_ALLOWLIST,
() -> SystemConfig.getInstance().getAllowIgnoreLocationSettings());
@@ -233,6 +238,21 @@ public class SystemSettingsHelper extends SettingsHelper {
}
@Override
+ public PackageTagsList getAdasAllowlist() {
+ return mAdasPackageAllowlist.getValue();
+ }
+
+ @Override
+ public void addAdasAllowlistChangedListener(GlobalSettingChangedListener listener) {
+ mAdasPackageAllowlist.addListener(listener);
+ }
+
+ @Override
+ public void removeAdasAllowlistChangedListener(GlobalSettingChangedListener listener) {
+ mAdasPackageAllowlist.removeListener(listener);
+ }
+
+ @Override
public PackageTagsList getIgnoreSettingsAllowlist() {
return mIgnoreSettingsPackageAllowlist.getValue();
}
@@ -359,11 +379,19 @@ public class SystemSettingsHelper extends SettingsHelper {
PackageTagsList ignoreSettingsAllowlist = mIgnoreSettingsPackageAllowlist.getValue();
if (!ignoreSettingsAllowlist.isEmpty()) {
- ipw.println("Bypass Allow Packages:");
+ ipw.println("Emergency Bypass Allow Packages:");
ipw.increaseIndent();
ignoreSettingsAllowlist.dump(ipw);
ipw.decreaseIndent();
}
+
+ PackageTagsList adasPackageAllowlist = mAdasPackageAllowlist.getValue();
+ if (!adasPackageAllowlist.isEmpty()) {
+ ipw.println("ADAS Bypass Allow Packages:");
+ ipw.increaseIndent();
+ adasPackageAllowlist.dump(ipw);
+ ipw.decreaseIndent();
+ }
}
private abstract static class ObservingSetting extends ContentObserver {
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 721ef1ed358f..1235352b0590 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -699,6 +699,9 @@ public class LocationProviderManager extends
} else if (!mLocationSettings.getUserSettings(
getIdentity().getUserId()).isAdasGnssLocationEnabled()) {
adasGnssBypass = false;
+ } else if (!mSettingsHelper.getAdasAllowlist().contains(
+ getIdentity().getPackageName(), getIdentity().getAttributionTag())) {
+ adasGnssBypass = false;
}
builder.setAdasGnssBypass(adasGnssBypass);
@@ -1406,6 +1409,8 @@ public class LocationProviderManager extends
this::onAppForegroundChanged;
private final GlobalSettingChangedListener mBackgroundThrottleIntervalChangedListener =
this::onBackgroundThrottleIntervalChanged;
+ private final GlobalSettingChangedListener mAdasPackageAllowlistChangedListener =
+ this::onAdasAllowlistChanged;
private final GlobalSettingChangedListener mIgnoreSettingsPackageWhitelistChangedListener =
this::onIgnoreSettingsWhitelistChanged;
private final LocationPowerSaveModeChangedListener mLocationPowerSaveModeChangedListener =
@@ -1710,6 +1715,9 @@ public class LocationProviderManager extends
} else if (!mLocationSettings.getUserSettings(
identity.getUserId()).isAdasGnssLocationEnabled()) {
adasGnssBypass = false;
+ } else if (!mSettingsHelper.getAdasAllowlist().contains(
+ identity.getPackageName(), identity.getAttributionTag())) {
+ adasGnssBypass = false;
}
builder.setAdasGnssBypass(adasGnssBypass);
@@ -1979,6 +1987,8 @@ public class LocationProviderManager extends
mBackgroundThrottlePackageWhitelistChangedListener);
mSettingsHelper.addOnLocationPackageBlacklistChangedListener(
mLocationPackageBlacklistChangedListener);
+ mSettingsHelper.addAdasAllowlistChangedListener(
+ mAdasPackageAllowlistChangedListener);
mSettingsHelper.addIgnoreSettingsAllowlistChangedListener(
mIgnoreSettingsPackageWhitelistChangedListener);
mLocationPermissionsHelper.addListener(mLocationPermissionsListener);
@@ -2000,6 +2010,7 @@ public class LocationProviderManager extends
mBackgroundThrottlePackageWhitelistChangedListener);
mSettingsHelper.removeOnLocationPackageBlacklistChangedListener(
mLocationPackageBlacklistChangedListener);
+ mSettingsHelper.removeAdasAllowlistChangedListener(mAdasPackageAllowlistChangedListener);
mSettingsHelper.removeIgnoreSettingsAllowlistChangedListener(
mIgnoreSettingsPackageWhitelistChangedListener);
mLocationPermissionsHelper.removeListener(mLocationPermissionsListener);
@@ -2422,6 +2433,12 @@ public class LocationProviderManager extends
}
}
+ private void onAdasAllowlistChanged() {
+ synchronized (mLock) {
+ updateRegistrations(Registration::onProviderLocationRequestChanged);
+ }
+ }
+
private void onIgnoreSettingsWhitelistChanged() {
synchronized (mLock) {
updateRegistrations(Registration::onProviderLocationRequestChanged);
diff --git a/services/core/java/com/android/server/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java
index 0aa384cedbf1..015bf3c5e390 100644
--- a/services/core/java/com/android/server/logcat/LogcatManagerService.java
+++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java
@@ -183,7 +183,8 @@ public final class LogcatManagerService extends SystemService {
ActivityManagerInternal ami = LocalServices.getService(
ActivityManagerInternal.class);
- boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(mUid);
+ boolean isCallerInstrumented =
+ ami.getInstrumentationSourceUid(mUid) != android.os.Process.INVALID_UID;
// The instrumented apks only run for testing, so we don't check user permission.
if (isCallerInstrumented) {
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
index 728782ccee0b..d0651ed176cf 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -133,6 +133,10 @@ class BluetoothRouteProvider {
mIntentFilter, null, null);
}
+ public void stop() {
+ mContext.unregisterReceiver(mBroadcastReceiver);
+ }
+
/**
* Transfers to a given bluetooth route.
* The dedicated BT device with the route would be activated.
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index b3072667130b..e27cbeaab139 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -1150,6 +1150,8 @@ class MediaRouter2ServiceImpl {
if (DEBUG) {
Slog.d(TAG, userRecord + ": Disposed");
}
+ userRecord.mHandler.sendMessage(
+ obtainMessage(UserHandler::stop, userRecord.mHandler));
mUserRecords.remove(userRecord.mUserId);
// Note: User already stopped (by switchUser) so no need to send stop message here.
}
@@ -1330,6 +1332,7 @@ class MediaRouter2ServiceImpl {
private void start() {
if (!mRunning) {
mRunning = true;
+ mSystemProvider.start();
mWatcher.start();
}
}
@@ -1338,6 +1341,7 @@ class MediaRouter2ServiceImpl {
if (mRunning) {
mRunning = false;
mWatcher.stop(); // also stops all providers
+ mSystemProvider.stop();
}
}
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 78781591a79e..d91bf8c00044 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -71,6 +71,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
private final IAudioService mAudioService;
private final Handler mHandler;
private final Context mContext;
+ private final UserHandle mUser;
private final BluetoothRouteProvider mBtRouteProvider;
private static ComponentName sComponentName = new ComponentName(
@@ -86,6 +87,9 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
int mDeviceVolume;
+ private final AudioManagerBroadcastReceiver mAudioReceiver =
+ new AudioManagerBroadcastReceiver();
+
private final Object mRequestLock = new Object();
@GuardedBy("mRequestLock")
private volatile SessionCreationRequest mPendingSessionCreationRequest;
@@ -108,6 +112,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
mIsSystemRouteProvider = true;
mContext = context;
+ mUser = user;
mHandler = new Handler(Looper.getMainLooper());
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
@@ -128,21 +133,33 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
}
});
updateSessionInfosIfNeeded();
+ }
+ public void start() {
IntentFilter intentFilter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
intentFilter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
- mContext.registerReceiverAsUser(new AudioManagerBroadcastReceiver(), user,
+ mContext.registerReceiverAsUser(mAudioReceiver, mUser,
intentFilter, null, null);
if (mBtRouteProvider != null) {
mHandler.post(() -> {
- mBtRouteProvider.start(user);
+ mBtRouteProvider.start(mUser);
notifyProviderState();
});
}
updateVolume();
}
+ public void stop() {
+ mContext.unregisterReceiver(mAudioReceiver);
+ if (mBtRouteProvider != null) {
+ mHandler.post(() -> {
+ mBtRouteProvider.stop();
+ notifyProviderState();
+ });
+ }
+ }
+
@Override
public void setCallback(Callback callback) {
super.setCallback(callback);
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 152c74553d32..29ee28175f57 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -52,7 +52,6 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.function.QuadFunction;
import com.android.server.FgThread;
-import com.android.server.LocalServices;
import com.android.server.compat.CompatChange;
import com.android.server.om.OverlayReferenceMapper;
import com.android.server.pm.parsing.pkg.AndroidPackage;
@@ -171,10 +170,13 @@ public class AppsFilter implements Watchable, Snappable {
* filtered to the second. It's essentially a cache of the
* {@link #shouldFilterApplicationInternal(int, Object, PackageStateInternal, int)} call.
* NOTE: It can only be relied upon after the system is ready to avoid unnecessary update on
- * initial scam and is null until {@link #onSystemReady()} is called.
+ * initial scam and is empty until {@link #mSystemReady} is true.
*/
@GuardedBy("mCacheLock")
- private volatile WatchedSparseBooleanMatrix mShouldFilterCache;
+ @NonNull
+ private final WatchedSparseBooleanMatrix mShouldFilterCache;
+
+ private volatile boolean mSystemReady = false;
/**
* A cached snapshot.
@@ -187,7 +189,8 @@ public class AppsFilter implements Watchable, Snappable {
public AppsFilter createSnapshot() {
AppsFilter s = new AppsFilter(mSource);
return s;
- }};
+ }
+ };
}
/**
@@ -219,6 +222,7 @@ public class AppsFilter implements Watchable, Snappable {
/**
* Return true if the {@link Watcher) is a registered observer.
+ *
* @param observer A {@link Watcher} that might be registered
* @return true if the observer is registered with this {@link Watchable}.
*/
@@ -262,6 +266,7 @@ public class AppsFilter implements Watchable, Snappable {
mStateProvider = stateProvider;
mPmInternal = pmInternal;
mBackgroundExecutor = backgroundExecutor;
+ mShouldFilterCache = new WatchedSparseBooleanMatrix();
mSnapshot = makeCache();
}
@@ -285,16 +290,14 @@ public class AppsFilter implements Watchable, Snappable {
mStateProvider = orig.mStateProvider;
mSystemSigningDetails = orig.mSystemSigningDetails;
mProtectedBroadcasts = orig.mProtectedBroadcasts;
- mShouldFilterCache = orig.mShouldFilterCache;
- if (mShouldFilterCache != null) {
- synchronized (orig.mCacheLock) {
- mShouldFilterCache = mShouldFilterCache.snapshot();
- }
+ synchronized (orig.mCacheLock) {
+ mShouldFilterCache = orig.mShouldFilterCache.snapshot();
}
mBackgroundExecutor = null;
mPmInternal = null;
mSnapshot = new SnapshotCache.Sealed<>();
+ mSystemReady = true;
}
/**
@@ -653,9 +656,9 @@ public class AppsFilter implements Watchable, Snappable {
* Grants access based on an interaction between a calling and target package, granting
* visibility of the caller from the target.
*
- * @param recipientUid the uid gaining visibility of the {@code visibleUid}.
- * @param visibleUid the uid becoming visible to the {@recipientUid}
- * @param retainOnUpdate if the implicit access retained across package updates.
+ * @param recipientUid the uid gaining visibility of the {@code visibleUid}.
+ * @param visibleUid the uid becoming visible to the {@recipientUid}
+ * @param retainOnUpdate if the implicit access retained across package updates.
* @return {@code true} if implicit access was not already granted.
*/
public boolean grantImplicitAccess(int recipientUid, int visibleUid, boolean retainOnUpdate) {
@@ -669,8 +672,9 @@ public class AppsFilter implements Watchable, Snappable {
Slog.i(TAG, (retainOnUpdate ? "retained " : "") + "implicit access granted: "
+ recipientUid + " -> " + visibleUid);
}
- synchronized (mCacheLock) {
- if (mShouldFilterCache != null) {
+
+ if (mSystemReady) {
+ synchronized (mCacheLock) {
// update the cache in a one-off manner since we've got all the information we
// need.
mShouldFilterCache.put(recipientUid, visibleUid, false);
@@ -688,13 +692,14 @@ public class AppsFilter implements Watchable, Snappable {
updateEntireShouldFilterCacheAsync();
onChanged();
+ mSystemReady = true;
}
/**
* Adds a package that should be considered when filtering visibility between apps.
*
* @param newPkgSetting the new setting being added
- * @param isReplace if the package is being replaced and may need extra cleanup.
+ * @param isReplace if the package is being replaced and may need extra cleanup.
*/
public void addPackage(PackageStateInternal newPkgSetting, boolean isReplace) {
if (DEBUG_TRACING) {
@@ -708,29 +713,27 @@ public class AppsFilter implements Watchable, Snappable {
mStateProvider.runWithState((settings, users) -> {
ArraySet<String> additionalChangedPackages =
addPackageInternal(newPkgSetting, settings);
- synchronized (mCacheLock) {
- if (mShouldFilterCache != null) {
- updateShouldFilterCacheForPackage(mShouldFilterCache, null, newPkgSetting,
- settings, users, USER_ALL, settings.size());
- if (additionalChangedPackages != null) {
- for (int index = 0; index < additionalChangedPackages.size(); index++) {
- String changedPackage = additionalChangedPackages.valueAt(index);
- PackageStateInternal changedPkgSetting =
- settings.get(changedPackage);
- if (changedPkgSetting == null) {
- // It's possible for the overlay mapper to know that an actor
- // package changed via an explicit reference, even if the actor
- // isn't installed, so skip if that's the case.
- continue;
- }
-
- updateShouldFilterCacheForPackage(mShouldFilterCache, null,
- changedPkgSetting, settings, users, USER_ALL,
- settings.size());
+ if (mSystemReady) {
+ updateShouldFilterCacheForPackage(null, newPkgSetting,
+ settings, users, USER_ALL, settings.size());
+ if (additionalChangedPackages != null) {
+ for (int index = 0; index < additionalChangedPackages.size(); index++) {
+ String changedPackage = additionalChangedPackages.valueAt(index);
+ PackageStateInternal changedPkgSetting =
+ settings.get(changedPackage);
+ if (changedPkgSetting == null) {
+ // It's possible for the overlay mapper to know that an actor
+ // package changed via an explicit reference, even if the actor
+ // isn't installed, so skip if that's the case.
+ continue;
}
+
+ updateShouldFilterCacheForPackage(null,
+ changedPkgSetting, settings, users, USER_ALL,
+ settings.size());
}
- } // else, rebuild entire cache when system is ready
- }
+ }
+ } // else, rebuild entire cache when system is ready
});
} finally {
onChanged();
@@ -845,19 +848,20 @@ public class AppsFilter implements Watchable, Snappable {
return changedPackages;
}
- @GuardedBy("mCacheLock")
private void removeAppIdFromVisibilityCache(int appId) {
- if (mShouldFilterCache == null) {
+ if (!mSystemReady) {
return;
}
- for (int i = 0; i < mShouldFilterCache.size(); i++) {
- if (UserHandle.getAppId(mShouldFilterCache.keyAt(i)) == appId) {
- mShouldFilterCache.removeAt(i);
- // The key was deleted so the list of keys has shifted left. That means i
- // is now pointing at the next key to be examined. The decrement here and
- // the loop increment together mean that i will be unchanged in the need
- // iteration and will correctly point to the next key to be examined.
- i--;
+ synchronized (mCacheLock) {
+ for (int i = 0; i < mShouldFilterCache.size(); i++) {
+ if (UserHandle.getAppId(mShouldFilterCache.keyAt(i)) == appId) {
+ mShouldFilterCache.removeAt(i);
+ // The key was deleted so the list of keys has shifted left. That means i
+ // is now pointing at the next key to be examined. The decrement here and
+ // the loop increment together mean that i will be unchanged in the need
+ // iteration and will correctly point to the next key to be examined.
+ i--;
+ }
}
}
}
@@ -880,31 +884,23 @@ public class AppsFilter implements Watchable, Snappable {
+ "updating the whole cache");
userId = USER_ALL;
}
- WatchedSparseBooleanMatrix cache =
- updateEntireShouldFilterCacheInner(settings, users, userId);
- synchronized (mCacheLock) {
- mShouldFilterCache = cache;
- }
+ updateEntireShouldFilterCacheInner(settings, users, userId);
});
}
- private WatchedSparseBooleanMatrix updateEntireShouldFilterCacheInner(
+ private void updateEntireShouldFilterCacheInner(
ArrayMap<String, ? extends PackageStateInternal> settings, UserInfo[] users,
int subjectUserId) {
- final WatchedSparseBooleanMatrix cache;
- if (subjectUserId == USER_ALL) {
- cache = new WatchedSparseBooleanMatrix(users.length * settings.size());
- } else {
- synchronized (mCacheLock) {
- cache = mShouldFilterCache.snapshot();
+ synchronized (mCacheLock) {
+ if (subjectUserId == USER_ALL) {
+ mShouldFilterCache.clear();
}
- cache.setCapacity(users.length * settings.size());
+ mShouldFilterCache.setCapacity(users.length * settings.size());
}
for (int i = settings.size() - 1; i >= 0; i--) {
- updateShouldFilterCacheForPackage(cache,
+ updateShouldFilterCacheForPackage(
null /*skipPackage*/, settings.valueAt(i), settings, users, subjectUserId, i);
}
- return cache;
}
private void updateEntireShouldFilterCacheAsync() {
@@ -923,8 +919,7 @@ public class AppsFilter implements Watchable, Snappable {
packagesCache.put(settings.keyAt(i), pkg);
}
});
- WatchedSparseBooleanMatrix cache = updateEntireShouldFilterCacheInner(
- settingsCopy, usersRef[0], USER_ALL);
+
boolean[] changed = new boolean[1];
// We have a cache, let's make sure the world hasn't changed out from under us.
mStateProvider.runWithState((settings, users) -> {
@@ -947,45 +942,39 @@ public class AppsFilter implements Watchable, Snappable {
Slog.i(TAG, "Rebuilding cache with lock due to package change.");
}
} else {
- synchronized (mCacheLock) {
- mShouldFilterCache = cache;
- }
+ updateEntireShouldFilterCacheInner(settingsCopy, usersRef[0], USER_ALL);
}
});
}
public void onUserCreated(int newUserId) {
- synchronized (mCacheLock) {
- if (mShouldFilterCache != null) {
- updateEntireShouldFilterCache(newUserId);
- onChanged();
- }
+ if (!mSystemReady) {
+ return;
}
+ updateEntireShouldFilterCache(newUserId);
+ onChanged();
}
public void onUserDeleted(@UserIdInt int userId) {
- synchronized (mCacheLock) {
- if (mShouldFilterCache != null) {
- removeShouldFilterCacheForUser(userId);
- onChanged();
- }
+ if (!mSystemReady) {
+ return;
}
+ removeShouldFilterCacheForUser(userId);
+ onChanged();
}
private void updateShouldFilterCacheForPackage(String packageName) {
mStateProvider.runWithState((settings, users) -> {
- synchronized (mCacheLock) {
- if (mShouldFilterCache == null) {
- return;
- }
- updateShouldFilterCacheForPackage(mShouldFilterCache, null /* skipPackage */,
- settings.get(packageName), settings, users, USER_ALL,
- settings.size() /*maxIndex*/);
+ if (!mSystemReady) {
+ return;
}
+ updateShouldFilterCacheForPackage(null /* skipPackage */,
+ settings.get(packageName), settings, users, USER_ALL,
+ settings.size() /*maxIndex*/);
});
}
- private void updateShouldFilterCacheForPackage(WatchedSparseBooleanMatrix cache,
+ private void updateShouldFilterCacheForPackage(
@Nullable String skipPackageName, PackageStateInternal subjectSetting, ArrayMap<String,
? extends PackageStateInternal> allSettings, UserInfo[] allUsers, int subjectUserId,
int maxIndex) {
@@ -1001,53 +990,56 @@ public class AppsFilter implements Watchable, Snappable {
}
if (subjectUserId == USER_ALL) {
for (int su = 0; su < allUsers.length; su++) {
- updateShouldFilterCacheForUser(cache, subjectSetting, allUsers, otherSetting,
+ updateShouldFilterCacheForUser(subjectSetting, allUsers, otherSetting,
allUsers[su].id);
}
} else {
- updateShouldFilterCacheForUser(cache, subjectSetting, allUsers, otherSetting,
+ updateShouldFilterCacheForUser(subjectSetting, allUsers, otherSetting,
subjectUserId);
}
}
}
- private void updateShouldFilterCacheForUser(WatchedSparseBooleanMatrix cache,
+ private void updateShouldFilterCacheForUser(
PackageStateInternal subjectSetting, UserInfo[] allUsers,
PackageStateInternal otherSetting, int subjectUserId) {
for (int ou = 0; ou < allUsers.length; ou++) {
int otherUser = allUsers[ou].id;
int subjectUid = UserHandle.getUid(subjectUserId, subjectSetting.getAppId());
int otherUid = UserHandle.getUid(otherUser, otherSetting.getAppId());
- cache.put(subjectUid, otherUid,
- shouldFilterApplicationInternal(
- subjectUid, subjectSetting, otherSetting, otherUser));
- cache.put(otherUid, subjectUid,
- shouldFilterApplicationInternal(
- otherUid, otherSetting, subjectSetting, subjectUserId));
+ final boolean shouldFilterSubjectToOther = shouldFilterApplicationInternal(
+ subjectUid, subjectSetting, otherSetting, otherUser);
+ final boolean shouldFilterOtherToSubject = shouldFilterApplicationInternal(
+ otherUid, otherSetting, subjectSetting, subjectUserId);
+ synchronized (mCacheLock) {
+ mShouldFilterCache.put(subjectUid, otherUid, shouldFilterSubjectToOther);
+ mShouldFilterCache.put(otherUid, subjectUid, shouldFilterOtherToSubject);
+ }
}
}
- @GuardedBy("mCacheLock")
private void removeShouldFilterCacheForUser(int userId) {
- // Sorted uids with the ascending order
- final int[] cacheUids = mShouldFilterCache.keys();
- final int size = cacheUids.length;
- int pos = Arrays.binarySearch(cacheUids, UserHandle.getUid(userId, 0));
- final int fromIndex = (pos >= 0 ? pos : ~pos);
- if (fromIndex >= size || UserHandle.getUserId(cacheUids[fromIndex]) != userId) {
- Slog.w(TAG, "Failed to remove should filter cache for user " + userId
- + ", fromIndex=" + fromIndex);
- return;
- }
- pos = Arrays.binarySearch(cacheUids, UserHandle.getUid(userId + 1, 0) - 1);
- final int toIndex = (pos >= 0 ? pos + 1 : ~pos);
- if (fromIndex >= toIndex || UserHandle.getUserId(cacheUids[toIndex - 1]) != userId) {
- Slog.w(TAG, "Failed to remove should filter cache for user " + userId
- + ", fromIndex=" + fromIndex + ", toIndex=" + toIndex);
- return;
+ synchronized (mCacheLock) {
+ // Sorted uids with the ascending order
+ final int[] cacheUids = mShouldFilterCache.keys();
+ final int size = cacheUids.length;
+ int pos = Arrays.binarySearch(cacheUids, UserHandle.getUid(userId, 0));
+ final int fromIndex = (pos >= 0 ? pos : ~pos);
+ if (fromIndex >= size || UserHandle.getUserId(cacheUids[fromIndex]) != userId) {
+ Slog.w(TAG, "Failed to remove should filter cache for user " + userId
+ + ", fromIndex=" + fromIndex);
+ return;
+ }
+ pos = Arrays.binarySearch(cacheUids, UserHandle.getUid(userId + 1, 0) - 1);
+ final int toIndex = (pos >= 0 ? pos + 1 : ~pos);
+ if (fromIndex >= toIndex || UserHandle.getUserId(cacheUids[toIndex - 1]) != userId) {
+ Slog.w(TAG, "Failed to remove should filter cache for user " + userId
+ + ", fromIndex=" + fromIndex + ", toIndex=" + toIndex);
+ return;
+ }
+ mShouldFilterCache.removeRange(fromIndex, toIndex);
+ mShouldFilterCache.compact();
}
- mShouldFilterCache.removeRange(fromIndex, toIndex);
- mShouldFilterCache.compact();
}
private static boolean isSystemSigned(@NonNull SigningDetails sysSigningDetails,
@@ -1171,6 +1163,7 @@ public class AppsFilter implements Watchable, Snappable {
/**
* Equivalent to calling {@link #addPackage(PackageStateInternal, boolean)} with
* {@code isReplace} equal to {@code false}.
+ *
* @see AppsFilter#addPackage(PackageStateInternal, boolean)
*/
public void addPackage(PackageStateInternal newPkgSetting) {
@@ -1180,7 +1173,7 @@ public class AppsFilter implements Watchable, Snappable {
/**
* Removes a package for consideration when filtering visibility between apps.
*
- * @param setting the setting of the package being removed.
+ * @param setting the setting of the package being removed.
* @param isReplace if the package is being replaced.
*/
public void removePackage(PackageStateInternal setting, boolean isReplace) {
@@ -1253,43 +1246,41 @@ public class AppsFilter implements Watchable, Snappable {
}
}
- synchronized (mCacheLock) {
- removeAppIdFromVisibilityCache(setting.getAppId());
- if (mShouldFilterCache != null && setting.hasSharedUser()) {
- final ArraySet<PackageStateInternal> sharedUserPackages =
- mPmInternal.getSharedUserPackages(setting.getSharedUserAppId());
- for (int i = sharedUserPackages.size() - 1; i >= 0; i--) {
- PackageStateInternal siblingSetting =
- sharedUserPackages.valueAt(i);
- if (siblingSetting == setting) {
- continue;
- }
- updateShouldFilterCacheForPackage(mShouldFilterCache,
- setting.getPackageName(), siblingSetting, settings, users,
- USER_ALL, settings.size());
+ removeAppIdFromVisibilityCache(setting.getAppId());
+ if (mSystemReady && setting.hasSharedUser()) {
+ final ArraySet<PackageStateInternal> sharedUserPackages =
+ mPmInternal.getSharedUserPackages(setting.getSharedUserAppId());
+ for (int i = sharedUserPackages.size() - 1; i >= 0; i--) {
+ PackageStateInternal siblingSetting =
+ sharedUserPackages.valueAt(i);
+ if (siblingSetting == setting) {
+ continue;
}
+ updateShouldFilterCacheForPackage(
+ setting.getPackageName(), siblingSetting, settings, users,
+ USER_ALL, settings.size());
}
+ }
- if (mShouldFilterCache != null) {
- if (additionalChangedPackages != null) {
- for (int index = 0; index < additionalChangedPackages.size(); index++) {
- String changedPackage = additionalChangedPackages.valueAt(index);
- PackageStateInternal changedPkgSetting = settings.get(changedPackage);
- if (changedPkgSetting == null) {
- // It's possible for the overlay mapper to know that an actor
- // package changed via an explicit reference, even if the actor
- // isn't installed, so skip if that's the case.
- continue;
- }
-
- updateShouldFilterCacheForPackage(mShouldFilterCache, null,
- changedPkgSetting, settings, users, USER_ALL, settings.size());
+ if (mSystemReady) {
+ if (additionalChangedPackages != null) {
+ for (int index = 0; index < additionalChangedPackages.size(); index++) {
+ String changedPackage = additionalChangedPackages.valueAt(index);
+ PackageStateInternal changedPkgSetting = settings.get(changedPackage);
+ if (changedPkgSetting == null) {
+ // It's possible for the overlay mapper to know that an actor
+ // package changed via an explicit reference, even if the actor
+ // isn't installed, so skip if that's the case.
+ continue;
}
+
+ updateShouldFilterCacheForPackage(null,
+ changedPkgSetting, settings, users, USER_ALL, settings.size());
}
}
-
- onChanged();
}
+
+ onChanged();
});
}
@@ -1315,29 +1306,16 @@ public class AppsFilter implements Watchable, Snappable {
|| callingAppId == targetPkgSetting.getAppId()) {
return false;
}
- synchronized (mCacheLock) {
- if (mShouldFilterCache != null) { // use cache
- final int callingIndex = mShouldFilterCache.indexOfKey(callingUid);
- if (callingIndex < 0) {
- Slog.wtf(TAG, "Encountered calling uid with no cached rules: "
- + callingUid);
- return true;
- }
- final int targetUid = UserHandle.getUid(userId, targetPkgSetting.getAppId());
- final int targetIndex = mShouldFilterCache.indexOfKey(targetUid);
- if (targetIndex < 0) {
- Slog.w(TAG, "Encountered calling -> target with no cached rules: "
- + callingUid + " -> " + targetUid);
- return true;
- }
- if (!mShouldFilterCache.valueAt(callingIndex, targetIndex)) {
- return false;
- }
- } else {
- if (!shouldFilterApplicationInternal(
- callingUid, callingSetting, targetPkgSetting, userId)) {
- return false;
- }
+ if (mSystemReady) { // use cache
+ if (!shouldFilterApplicationUsingCache(callingUid,
+ targetPkgSetting.getAppId(),
+ userId)) {
+ return false;
+ }
+ } else {
+ if (!shouldFilterApplicationInternal(
+ callingUid, callingSetting, targetPkgSetting, userId)) {
+ return false;
}
}
if (DEBUG_LOGGING || mFeatureConfig.isLoggingEnabled(callingAppId)) {
@@ -1351,6 +1329,25 @@ public class AppsFilter implements Watchable, Snappable {
}
}
+ private boolean shouldFilterApplicationUsingCache(int callingUid, int appId, int userId) {
+ synchronized (mCacheLock) {
+ final int callingIndex = mShouldFilterCache.indexOfKey(callingUid);
+ if (callingIndex < 0) {
+ Slog.wtf(TAG, "Encountered calling uid with no cached rules: "
+ + callingUid);
+ return true;
+ }
+ final int targetUid = UserHandle.getUid(userId, appId);
+ final int targetIndex = mShouldFilterCache.indexOfKey(targetUid);
+ if (targetIndex < 0) {
+ Slog.w(TAG, "Encountered calling -> target with no cached rules: "
+ + callingUid + " -> " + targetUid);
+ return true;
+ }
+ return mShouldFilterCache.valueAt(callingIndex, targetIndex);
+ }
+ }
+
private boolean shouldFilterApplicationInternal(int callingUid, Object callingSetting,
PackageStateInternal targetPkgSetting, int targetUserId) {
if (DEBUG_TRACING) {
@@ -1437,10 +1434,10 @@ public class AppsFilter implements Watchable, Snappable {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "requestsQueryAllPackages");
}
if (callingPkgSetting != null) {
- if (callingPkgSetting.getPkg() != null
- && requestsQueryAllPackages(callingPkgSetting.getPkg())) {
- return false;
- }
+ if (callingPkgSetting.getPkg() != null
+ && requestsQueryAllPackages(callingPkgSetting.getPkg())) {
+ return false;
+ }
} else {
for (int i = callingSharedPkgSettings.size() - 1; i >= 0; i--) {
AndroidPackage pkg = callingSharedPkgSettings.valueAt(i).getPkg();
@@ -1747,6 +1744,7 @@ public class AppsFilter implements Watchable, Snappable {
private interface ToString<T> {
String toString(T input);
+
}
private static <T> void dumpPackageSet(PrintWriter pw, @Nullable T filteringId,
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index bf1196d6b969..ed71f1eb5313 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -20,7 +20,6 @@ import static android.os.PowerExemptionManager.REASON_LOCKED_BOOT_COMPLETED;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
-import static com.android.server.pm.PackageManagerService.EMPTY_INT_ARRAY;
import static com.android.server.pm.PackageManagerService.PACKAGE_SCHEME;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import static com.android.server.pm.PackageManagerService.TAG;
@@ -86,11 +85,14 @@ public final class BroadcastHelper {
} else {
resolvedUserIds = userIds;
}
- doSendBroadcast(action, pkg, extras, flags, targetPkg, finishedReceiver,
- resolvedUserIds, false, broadcastAllowList, bOptions);
- if (instantUserIds != null && instantUserIds != EMPTY_INT_ARRAY) {
+
+ if (ArrayUtils.isEmpty(instantUserIds)) {
+ doSendBroadcast(action, pkg, extras, flags, targetPkg, finishedReceiver,
+ resolvedUserIds, false /* isInstantApp */, broadcastAllowList, bOptions);
+ } else {
+ // send restricted broadcasts for instant apps
doSendBroadcast(action, pkg, extras, flags, targetPkg, finishedReceiver,
- instantUserIds, true, null, bOptions);
+ instantUserIds, true /* isInstantApp */, null, bOptions);
}
} catch (RemoteException ex) {
}
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index bb2ba5cc498d..249099d122a1 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -358,9 +358,7 @@ final class DexOptHelper {
}
final long callingId = Binder.clearCallingIdentity();
try {
- synchronized (mPm.mInstallLock) {
- return performDexOptInternalWithDependenciesLI(p, pkgSetting, options);
- }
+ return performDexOptInternalWithDependenciesLI(p, pkgSetting, options);
} finally {
Binder.restoreCallingIdentity(callingId);
}
@@ -429,20 +427,18 @@ final class DexOptHelper {
throw new IllegalArgumentException("Unknown package: " + packageName);
}
- synchronized (mPm.mInstallLock) {
- Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
- // Whoever is calling forceDexOpt wants a compiled package.
- // Don't use profiles since that may cause compilation to be skipped.
- final int res = performDexOptInternalWithDependenciesLI(pkg, packageState,
- new DexoptOptions(packageName,
- getDefaultCompilerFilter(),
- DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE));
+ // Whoever is calling forceDexOpt wants a compiled package.
+ // Don't use profiles since that may cause compilation to be skipped.
+ final int res = performDexOptInternalWithDependenciesLI(pkg, packageState,
+ new DexoptOptions(packageName,
+ getDefaultCompilerFilter(),
+ DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE));
- Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
- if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) {
- throw new IllegalStateException("Failed to dexopt: " + res);
- }
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) {
+ throw new IllegalStateException("Failed to dexopt: " + res);
}
}
diff --git a/services/core/java/com/android/server/pm/InitAppsHelper.java b/services/core/java/com/android/server/pm/InitAppsHelper.java
index 15f26e7a6d6c..154f32a2e436 100644
--- a/services/core/java/com/android/server/pm/InitAppsHelper.java
+++ b/services/core/java/com/android/server/pm/InitAppsHelper.java
@@ -30,7 +30,7 @@ import static com.android.server.pm.PackageManagerService.SCAN_NO_DEX;
import static com.android.server.pm.PackageManagerService.SCAN_REQUIRE_KNOWN;
import static com.android.server.pm.PackageManagerService.SYSTEM_PARTITIONS;
import static com.android.server.pm.PackageManagerService.TAG;
-import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.PARSE_CHECK_MAX_SDK_VERSION;
+import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.PARSE_APK_IN_APEX;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -359,7 +359,7 @@ final class InitAppsHelper {
try {
if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
// when scanning apk in apexes, we want to check the maxSdkVersion
- parseFlags |= PARSE_CHECK_MAX_SDK_VERSION;
+ parseFlags |= PARSE_APK_IN_APEX;
}
mInstallPackageHelper.installPackagesFromDir(scanDir, frameworkSplits, parseFlags,
scanFlags, packageParser, executorService);
diff --git a/services/core/java/com/android/server/pm/IntentResolverInterceptor.java b/services/core/java/com/android/server/pm/IntentResolverInterceptor.java
index 603badb276b2..f5eee5ac160d 100644
--- a/services/core/java/com/android/server/pm/IntentResolverInterceptor.java
+++ b/services/core/java/com/android/server/pm/IntentResolverInterceptor.java
@@ -23,11 +23,12 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
import android.content.res.Resources;
import android.provider.DeviceConfig;
+import android.util.Slog;
import com.android.internal.R;
+import com.android.internal.app.ChooserActivity;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.server.LocalServices;
import com.android.server.wm.ActivityInterceptorCallback;
@@ -35,25 +36,28 @@ import com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo
import com.android.server.wm.ActivityTaskManagerInternal;
/**
- * Service to register an {@code ActivityInterceptorCallback} that modifies any {@code Intent}
- * that's being used to launch a user-space {@code ChooserActivity} by setting the destination
- * component to the delegated component when appropriate.
+ * Redirects Activity starts for the system bundled {@link ChooserActivity} to an external
+ * Sharesheet implementation by modifying the target component when appropriate.
+ * <p>
+ * Note: config_chooserActivity (Used also by ActivityTaskSupervisor) is already updated to point
+ * to the new instance. This value is read and used for the new target component.
*/
public final class IntentResolverInterceptor {
private static final String TAG = "IntentResolverIntercept";
-
private final Context mContext;
- private boolean mUseDelegateChooser;
+ private final ComponentName mFrameworkChooserComponent;
+ private final ComponentName mUnbundledChooserComponent;
+ private boolean mUseUnbundledSharesheet;
private final ActivityInterceptorCallback mActivityInterceptorCallback =
new ActivityInterceptorCallback() {
@Nullable
@Override
public ActivityInterceptResult intercept(ActivityInterceptorInfo info) {
- if (mUseDelegateChooser && isChooserActivity(info)) {
- return new ActivityInterceptResult(
- modifyChooserIntent(info.intent),
- info.checkedOptions);
+ if (mUseUnbundledSharesheet && isSystemChooserActivity(info)) {
+ Slog.d(TAG, "Redirecting to UNBUNDLED Sharesheet");
+ info.intent.setComponent(mUnbundledChooserComponent);
+ return new ActivityInterceptResult(info.intent, info.checkedOptions);
}
return null;
}
@@ -61,10 +65,13 @@ public final class IntentResolverInterceptor {
public IntentResolverInterceptor(Context context) {
mContext = context;
+ mFrameworkChooserComponent = new ComponentName(mContext, ChooserActivity.class);
+ mUnbundledChooserComponent = ComponentName.unflattenFromString(
+ Resources.getSystem().getString(R.string.config_chooserActivity));
}
/**
- * Start listening for intents and USE_DELEGATE_CHOOSER property changes.
+ * Start listening for intents and USE_UNBUNDLED_SHARESHEET property changes.
*/
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
public void registerListeners() {
@@ -73,36 +80,25 @@ public final class IntentResolverInterceptor {
mActivityInterceptorCallback);
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
- mContext.getMainExecutor(), properties -> updateUseDelegateChooser());
- updateUseDelegateChooser();
+ mContext.getMainExecutor(), properties -> updateUseUnbundledSharesheet());
+ updateUseUnbundledSharesheet();
}
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
- private void updateUseDelegateChooser() {
- mUseDelegateChooser = DeviceConfig.getBoolean(
+ private void updateUseUnbundledSharesheet() {
+ mUseUnbundledSharesheet = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.USE_UNBUNDLED_SHARESHEET,
false);
+ if (mUseUnbundledSharesheet) {
+ Slog.d(TAG, "using UNBUNDLED Sharesheet");
+ } else {
+ Slog.d(TAG, "using FRAMEWORK Sharesheet");
+ }
}
- private Intent modifyChooserIntent(Intent intent) {
- intent.setComponent(getUnbundledChooserComponentName());
- return intent;
- }
-
- private static boolean isChooserActivity(ActivityInterceptorInfo info) {
- ComponentName targetComponent = new ComponentName(info.aInfo.packageName, info.aInfo.name);
-
- return targetComponent.equals(getSystemChooserComponentName())
- || targetComponent.equals(getUnbundledChooserComponentName());
- }
-
- private static ComponentName getSystemChooserComponentName() {
- return new ComponentName("android", "com.android.internal.app.ChooserActivity");
- }
-
- private static ComponentName getUnbundledChooserComponentName() {
- return ComponentName.unflattenFromString(
- Resources.getSystem().getString(R.string.config_chooserActivity));
+ private boolean isSystemChooserActivity(ActivityInterceptorInfo info) {
+ return mFrameworkChooserComponent.getPackageName().equals(info.aInfo.packageName)
+ && mFrameworkChooserComponent.getClassName().equals(info.aInfo.name);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index bf80a466e034..f6d8d72cc382 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3536,6 +3536,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
EventLog.writeEvent(0x534e4554, "145981139", packageInfo.applicationInfo.uid,
"");
}
+ Log.w(TAG, "Missing required system package: " + packageName + (packageInfo != null
+ ? ", but found with extended search." : "."));
return null;
}
} finally {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index e142ed631092..d6ab78bd27ea 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -3694,7 +3694,7 @@ class PackageManagerShellCommand extends ShellCommand {
}
List<PermissionInfo> ps = mPermissionManager
.queryPermissionsByGroup(groupList.get(i), 0 /*flags*/);
- final int count = ps.size();
+ final int count = (ps == null ? 0 : ps.size());
boolean first = true;
for (int p = 0 ; p < count ; p++) {
PermissionInfo pi = ps.get(p);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index d340561c2862..ee0fdc07f841 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -5413,190 +5413,170 @@ public class UserManagerService extends IUserManager.Stub {
(new Shell()).exec(this, in, out, err, args, callback, resultReceiver);
}
- private static final String PREFIX_HELP_COMMAND = " ";
- private static final String PREFIX_HELP_DESCRIPTION = " ";
- private static final String PREFIX_HELP_DESCRIPTION_EXTRA_LINES = " ";
-
- private static final String CMD_HELP = "help";
- private static final String CMD_LIST = "list";
- private static final String CMD_REPORT_SYSTEM_USER_PACKAGE_ALLOWLIST_PROBLEMS =
- "report-system-user-package-whitelist-problems";
-
- private static final String ARG_V = "-v";
- private static final String ARG_VERBOSE = "--verbose";
- private static final String ARG_ALL = "--all";
- private static final String ARG_CRITICAL_ONLY = "--critical-only";
- private static final String ARG_MODE = "--mode";
-
private final class Shell extends ShellCommand {
- @Override
- public void onHelp() {
- final PrintWriter pw = getOutPrintWriter();
- pw.printf("User manager (user) commands:\n");
-
- pw.printf("%s%s\n", PREFIX_HELP_COMMAND, CMD_HELP);
- pw.printf("%sPrints this help text.\n\n", PREFIX_HELP_DESCRIPTION);
-
- pw.printf("%s%s [%s] [%s]\n", PREFIX_HELP_COMMAND, CMD_LIST, ARG_V, ARG_ALL);
- pw.printf("%sPrints all users on the system.\n\n", PREFIX_HELP_DESCRIPTION);
-
- pw.printf("%s%s [%s | %s] [%s] [%s MODE]\n", PREFIX_HELP_COMMAND,
- CMD_REPORT_SYSTEM_USER_PACKAGE_ALLOWLIST_PROBLEMS,
- ARG_V, ARG_VERBOSE, ARG_CRITICAL_ONLY, ARG_MODE);
-
- pw.printf("%sReports all issues on user-type package allowlist XML files. Options:\n",
- PREFIX_HELP_DESCRIPTION);
- pw.printf("%s%s | %s: shows extra info, like number of issues\n",
- PREFIX_HELP_DESCRIPTION, ARG_V, ARG_VERBOSE);
- pw.printf("%s%s: show only critical issues, excluding warnings\n",
- PREFIX_HELP_DESCRIPTION, ARG_CRITICAL_ONLY);
- pw.printf("%s%s MODE: shows what errors would be if device used mode MODE\n"
- + "%s(where MODE is the allowlist mode integer as defined by "
- + "config_userTypePackageWhitelistMode)\n\n",
- PREFIX_HELP_DESCRIPTION, ARG_MODE, PREFIX_HELP_DESCRIPTION_EXTRA_LINES);
- }
-
- @Override
- public int onCommand(String cmd) {
- if (cmd == null) {
- return handleDefaultCommands(cmd);
+ @Override
+ public void onHelp() {
+ final PrintWriter pw = getOutPrintWriter();
+ pw.println("User manager (user) commands:");
+ pw.println(" help");
+ pw.println(" Prints this help text.");
+ pw.println();
+ pw.println(" list [-v | --verbose] [--all]");
+ pw.println(" Prints all users on the system.");
+ pw.println();
+ pw.println(" report-system-user-package-whitelist-problems [-v | --verbose] "
+ + "[--critical-only] [--mode MODE]");
+ pw.println(" Reports all issues on user-type package allowlist XML files. Options:");
+ pw.println(" -v | --verbose: shows extra info, like number of issues");
+ pw.println(" --critical-only: show only critical issues, excluding warnings");
+ pw.println(" --mode MODE: shows what errors would be if device used mode MODE");
+ pw.println(" (where MODE is the allowlist mode integer as defined by "
+ + "config_userTypePackageWhitelistMode)");
}
- try {
- switch(cmd) {
- case CMD_LIST:
- return runList();
- case CMD_REPORT_SYSTEM_USER_PACKAGE_ALLOWLIST_PROBLEMS:
- return runReportPackageAllowlistProblems();
- default:
- return handleDefaultCommands(cmd);
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
}
- } catch (RemoteException e) {
- getOutPrintWriter().println("Remote exception: " + e);
+
+ try {
+ switch(cmd) {
+ case "list":
+ return runList();
+ case "report-system-user-package-whitelist-problems":
+ return runReportPackageAllowlistProblems();
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ } catch (RemoteException e) {
+ getOutPrintWriter().println("Remote exception: " + e);
+ }
+ return -1;
}
- return -1;
- }
- private int runList() throws RemoteException {
- final PrintWriter pw = getOutPrintWriter();
- boolean all = false;
- boolean verbose = false;
- String opt;
- while ((opt = getNextOption()) != null) {
- switch (opt) {
- case ARG_V:
- verbose = true;
- break;
- case ARG_ALL:
- all = true;
- break;
- default:
- pw.println("Invalid option: " + opt);
- return -1;
- }
- }
- final IActivityManager am = ActivityManager.getService();
- final List<UserInfo> users = getUsers(/* excludePartial= */ !all,
- /* excludingDying=*/ false, /* excludePreCreated= */ !all);
- if (users == null) {
- pw.println("Error: couldn't get users");
- return 1;
- } else {
- final int size = users.size();
- int currentUser = UserHandle.USER_NULL;
- if (verbose) {
- pw.printf("%d users:\n\n", size);
- currentUser = am.getCurrentUser().id;
- } else {
- // NOTE: the standard "list users" command is used by integration tests and
- // hence should not be changed. If you need to add more info, use the
- // verbose option.
- pw.println("Users:");
+ private int runList() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ boolean all = false;
+ boolean verbose = false;
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-v":
+ case "--verbose":
+ verbose = true;
+ break;
+ case "--all":
+ all = true;
+ break;
+ default:
+ pw.println("Invalid option: " + opt);
+ return -1;
+ }
}
- for (int i = 0; i < size; i++) {
- final UserInfo user = users.get(i);
- final boolean running = am.isUserRunning(user.id, 0);
- final boolean current = user.id == currentUser;
- final boolean hasParent = user.profileGroupId != user.id
- && user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID;
+ final IActivityManager am = ActivityManager.getService();
+ final List<UserInfo> users = getUsers(/* excludePartial= */ !all,
+ /* excludeDying= */ false, /* excludePreCreated= */ !all);
+ if (users == null) {
+ pw.println("Error: couldn't get users");
+ return 1;
+ } else {
+ final int size = users.size();
+ int currentUser = UserHandle.USER_NULL;
if (verbose) {
- final DevicePolicyManagerInternal dpm = getDevicePolicyManagerInternal();
- String deviceOwner = "";
- String profileOwner = "";
- if (dpm != null) {
- final long ident = Binder.clearCallingIdentity();
- // NOTE: dpm methods below CANNOT be called while holding the mUsersLock
- try {
- if (dpm.getDeviceOwnerUserId() == user.id) {
- deviceOwner = " (device-owner)";
- }
- if (dpm.getProfileOwnerAsUser(user.id) != null) {
- profileOwner = " (profile-owner)";
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- pw.printf("%d: id=%d, name=%s, type=%s, flags=%s%s%s%s%s%s%s%s%s\n",
- i,
- user.id,
- user.name,
- user.userType.replace("android.os.usertype.", ""),
- UserInfo.flagsToString(user.flags),
- hasParent ? " (parentId=" + user.profileGroupId + ")" : "",
- running ? " (running)" : "",
- user.partial ? " (partial)" : "",
- user.preCreated ? " (pre-created)" : "",
- user.convertedFromPreCreated ? " (converted)" : "",
- deviceOwner, profileOwner,
- current ? " (current)" : "");
+ pw.printf("%d users:\n\n", size);
+ currentUser = am.getCurrentUser().id;
} else {
// NOTE: the standard "list users" command is used by integration tests and
// hence should not be changed. If you need to add more info, use the
// verbose option.
- pw.printf("\t%s%s\n", user, running ? " running" : "");
+ pw.println("Users:");
}
+ for (int i = 0; i < size; i++) {
+ final UserInfo user = users.get(i);
+ final boolean running = am.isUserRunning(user.id, 0);
+ final boolean current = user.id == currentUser;
+ final boolean hasParent = user.profileGroupId != user.id
+ && user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID;
+ if (verbose) {
+ final DevicePolicyManagerInternal dpm = getDevicePolicyManagerInternal();
+ String deviceOwner = "";
+ String profileOwner = "";
+ if (dpm != null) {
+ final long ident = Binder.clearCallingIdentity();
+ // NOTE: dpm methods below CANNOT be called while holding the mUsersLock
+ try {
+ if (dpm.getDeviceOwnerUserId() == user.id) {
+ deviceOwner = " (device-owner)";
+ }
+ if (dpm.getProfileOwnerAsUser(user.id) != null) {
+ profileOwner = " (profile-owner)";
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ pw.printf("%d: id=%d, name=%s, type=%s, flags=%s%s%s%s%s%s%s%s%s\n",
+ i,
+ user.id,
+ user.name,
+ user.userType.replace("android.os.usertype.", ""),
+ UserInfo.flagsToString(user.flags),
+ hasParent ? " (parentId=" + user.profileGroupId + ")" : "",
+ running ? " (running)" : "",
+ user.partial ? " (partial)" : "",
+ user.preCreated ? " (pre-created)" : "",
+ user.convertedFromPreCreated ? " (converted)" : "",
+ deviceOwner, profileOwner,
+ current ? " (current)" : "");
+ } else {
+ // NOTE: the standard "list users" command is used by integration tests and
+ // hence should not be changed. If you need to add more info, use the
+ // verbose option.
+ pw.printf("\t%s%s\n", user, running ? " running" : "");
+ }
+ }
+ return 0;
}
- return 0;
}
- }
- private int runReportPackageAllowlistProblems() {
- final PrintWriter pw = getOutPrintWriter();
- boolean verbose = false;
- boolean criticalOnly = false;
- int mode = UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_NONE;
- String opt;
- while ((opt = getNextOption()) != null) {
- switch (opt) {
- case ARG_V:
- case ARG_VERBOSE:
- verbose = true;
- break;
- case ARG_CRITICAL_ONLY:
- criticalOnly = true;
- break;
- case ARG_MODE:
- mode = Integer.parseInt(getNextArgRequired());
- break;
- default:
- pw.println("Invalid option: " + opt);
- return -1;
+ private int runReportPackageAllowlistProblems() {
+ final PrintWriter pw = getOutPrintWriter();
+ boolean verbose = false;
+ boolean criticalOnly = false;
+ int mode = UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_NONE;
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-v":
+ case "--verbose":
+ verbose = true;
+ break;
+ case "--critical-only":
+ criticalOnly = true;
+ break;
+ case "--mode":
+ mode = Integer.parseInt(getNextArgRequired());
+ break;
+ default:
+ pw.println("Invalid option: " + opt);
+ return -1;
+ }
}
- }
- Slog.d(LOG_TAG, "runReportPackageAllowlistProblems(): verbose=" + verbose
- + ", criticalOnly=" + criticalOnly
- + ", mode=" + UserSystemPackageInstaller.modeToString(mode));
+ Slog.d(LOG_TAG, "runReportPackageAllowlistProblems(): verbose=" + verbose
+ + ", criticalOnly=" + criticalOnly
+ + ", mode=" + UserSystemPackageInstaller.modeToString(mode));
- try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ")) {
- mSystemPackageInstaller.dumpPackageWhitelistProblems(ipw, mode, verbose,
- criticalOnly);
+ try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ")) {
+ mSystemPackageInstaller.dumpPackageWhitelistProblems(ipw, mode, verbose,
+ criticalOnly);
+ }
+ return 0;
}
- return 0;
- }
- }
+
+ } // class Shell
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index 7e4da945709b..4fae6b8e1271 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -45,7 +45,6 @@ import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.RoSystemProperties;
import com.android.internal.util.ArrayUtils;
@@ -94,8 +93,6 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub {
private final Context mContext;
private IPackageManager mPackageManager;
- private final Object mInstallLock;
- @GuardedBy("mInstallLock")
private final Installer mInstaller;
private final Handler mHandler;
@@ -105,10 +102,9 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub {
}
public ArtManagerService(Context context, Installer installer,
- Object installLock) {
+ Object ignored) {
mContext = context;
mInstaller = installer;
- mInstallLock = installLock;
mHandler = new Handler(BackgroundThread.getHandler().getLooper());
LocalServices.addService(ArtManagerInternal.class, new ArtManagerInternalImpl());
@@ -273,16 +269,14 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub {
private void createProfileSnapshot(String packageName, String profileName, String classpath,
int appId, ISnapshotRuntimeProfileCallback callback) {
// Ask the installer to snapshot the profile.
- synchronized (mInstallLock) {
- try {
- if (!mInstaller.createProfileSnapshot(appId, packageName, profileName, classpath)) {
- postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
- return;
- }
- } catch (InstallerException e) {
+ try {
+ if (!mInstaller.createProfileSnapshot(appId, packageName, profileName, classpath)) {
postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
return;
}
+ } catch (InstallerException e) {
+ postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
+ return;
}
// Open the snapshot and invoke the callback.
@@ -308,13 +302,11 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub {
Slog.d(TAG, "Destroying profile snapshot for" + packageName + ":" + profileName);
}
- synchronized (mInstallLock) {
- try {
- mInstaller.destroyProfileSnapshot(packageName, profileName);
- } catch (InstallerException e) {
- Slog.e(TAG, "Failed to destroy profile snapshot for " +
- packageName + ":" + profileName, e);
- }
+ try {
+ mInstaller.destroyProfileSnapshot(packageName, profileName);
+ } catch (InstallerException e) {
+ Slog.e(TAG, "Failed to destroy profile snapshot for " + packageName + ":" + profileName,
+ e);
}
}
@@ -480,9 +472,7 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub {
for (int i = packageProfileNames.size() - 1; i >= 0; i--) {
String codePath = packageProfileNames.keyAt(i);
String profileName = packageProfileNames.valueAt(i);
- synchronized (mInstallLock) {
- mInstaller.dumpProfiles(sharedGid, pkg.getPackageName(), profileName, codePath);
- }
+ mInstaller.dumpProfiles(sharedGid, pkg.getPackageName(), profileName, codePath);
}
} catch (InstallerException e) {
Slog.w(TAG, "Failed to dump profiles", e);
@@ -512,10 +502,8 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub {
") to " + outDexFile);
final long callingId = Binder.clearCallingIdentity();
try {
- synchronized (mInstallLock) {
- return mInstaller.compileLayouts(apkPath, packageName, outDexFile,
- pkg.getUid());
- }
+ return mInstaller.compileLayouts(apkPath, packageName, outDexFile,
+ pkg.getUid());
} finally {
Binder.restoreCallingIdentity(callingId);
}
diff --git a/services/core/java/com/android/server/pm/parsing/library/ApexSharedLibraryUpdater.java b/services/core/java/com/android/server/pm/parsing/library/ApexSharedLibraryUpdater.java
index 0418afbf29ee..1a2ff264319e 100644
--- a/services/core/java/com/android/server/pm/parsing/library/ApexSharedLibraryUpdater.java
+++ b/services/core/java/com/android/server/pm/parsing/library/ApexSharedLibraryUpdater.java
@@ -19,6 +19,7 @@ package com.android.server.pm.parsing.library;
import android.util.ArrayMap;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.UnboundedSdkLevel;
import com.android.server.SystemConfig;
import com.android.server.pm.parsing.pkg.ParsedPackage;
@@ -51,8 +52,11 @@ public class ApexSharedLibraryUpdater extends PackageSharedLibraryUpdater {
private void updateSharedLibraryForPackage(SystemConfig.SharedLibraryEntry entry,
ParsedPackage parsedPackage) {
- if (entry.onBootclasspathBefore != 0
- && parsedPackage.getTargetSdkVersion() < entry.onBootclasspathBefore) {
+ if (entry.onBootclasspathBefore != null
+ && isTargetSdkAtMost(
+ parsedPackage.getTargetSdkVersion(),
+ entry.onBootclasspathBefore)
+ && UnboundedSdkLevel.isAtLeast(entry.onBootclasspathBefore)) {
// this package targets an API where this library was in the BCP, so add
// the library transparently in case the package is using it
prefixRequiredLibrary(parsedPackage, entry.name);
@@ -64,4 +68,19 @@ public class ApexSharedLibraryUpdater extends PackageSharedLibraryUpdater {
removeLibrary(parsedPackage, entry.name);
}
}
+
+ private static boolean isTargetSdkAtMost(int targetSdk, String onBcpBefore) {
+ if (isCodename(onBcpBefore)) {
+ return targetSdk < 10000;
+ }
+ return targetSdk < Integer.parseInt(onBcpBefore);
+ }
+
+ private static boolean isCodename(String version) {
+ if (version.length() == 0) {
+ throw new IllegalArgumentException();
+ }
+ // assume Android codenames start with upper case letters.
+ return Character.isUpperCase((version.charAt(0)));
+ }
}
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index f255db4a9801..9897c42e4cec 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -209,8 +209,6 @@ public class ParsingPackageUtils {
public static final int SDK_VERSION = Build.VERSION.SDK_INT;
public static final String[] SDK_CODENAMES = Build.VERSION.ACTIVE_CODENAMES;
- public static final String[] PREVIOUS_CODENAMES =
- Build.VERSION.KNOWN_CODENAMES.toArray(new String[]{});
public static boolean sCompatibilityModeEnabled = true;
public static boolean sUseRoundIcon = false;
@@ -238,7 +236,7 @@ public class ParsingPackageUtils {
*/
public static final int PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY = 1 << 7;
public static final int PARSE_FRAMEWORK_RES_SPLITS = 1 << 8;
- public static final int PARSE_CHECK_MAX_SDK_VERSION = 1 << 9;
+ public static final int PARSE_APK_IN_APEX = 1 << 9;
public static final int PARSE_CHATTY = 1 << 31;
@@ -1534,7 +1532,7 @@ public class ParsingPackageUtils {
ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags)
throws IOException, XmlPullParserException {
if (SDK_VERSION > 0) {
- final boolean checkMaxSdkVersion = (flags & PARSE_CHECK_MAX_SDK_VERSION) != 0;
+ final boolean isApkInApex = (flags & PARSE_APK_IN_APEX) != 0;
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesSdk);
try {
int minVers = ParsingUtils.DEFAULT_MIN_SDK_VERSION;
@@ -1569,7 +1567,7 @@ public class ParsingPackageUtils {
targetCode = minCode;
}
- if (checkMaxSdkVersion) {
+ if (isApkInApex) {
val = sa.peekValue(R.styleable.AndroidManifestUsesSdk_maxSdkVersion);
if (val != null) {
// maxSdkVersion only supports integer
@@ -1578,7 +1576,8 @@ public class ParsingPackageUtils {
}
ParseResult<Integer> targetSdkVersionResult = FrameworkParsingPackageUtils
- .computeTargetSdkVersion(targetVers, targetCode, SDK_CODENAMES, input);
+ .computeTargetSdkVersion(targetVers, targetCode, SDK_CODENAMES, input,
+ isApkInApex);
if (targetSdkVersionResult.isError()) {
return input.error(targetSdkVersionResult);
}
@@ -1601,7 +1600,7 @@ public class ParsingPackageUtils {
pkg.setMinSdkVersion(minSdkVersion)
.setTargetSdkVersion(targetSdkVersion);
- if (checkMaxSdkVersion) {
+ if (isApkInApex) {
ParseResult<Integer> maxSdkVersionResult = FrameworkParsingPackageUtils
.computeMaxSdkVersion(maxVers, SDK_VERSION, input);
if (maxSdkVersionResult.isError()) {
diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index 70d69c663572..e157a277366f 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -187,8 +187,12 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat
initializeActivityRecognizersTags();
- // If this device does not have telephony, restrict the phone call ops
- if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ // Restrict phone call ops if the TelecomService will not start (conditioned on having
+ // FEATURE_MICROPHONE, FEATURE_TELECOM, or FEATURE_TELEPHONY).
+ PackageManager pm = mContext.getPackageManager();
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
+ && !pm.hasSystemFeature(PackageManager.FEATURE_MICROPHONE)
+ && !pm.hasSystemFeature(PackageManager.FEATURE_TELECOM)) {
AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
appOps.setUserRestrictionForUser(AppOpsManager.OP_PHONE_CALL_MICROPHONE, true, mToken,
null, UserHandle.USER_ALL);
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index 2f68f56a7de0..bce1cce0f47f 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -29,6 +29,7 @@ import static android.app.AppOpsManager.OP_RECORD_AUDIO;
import static android.content.Intent.EXTRA_PACKAGE_NAME;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.hardware.SensorPrivacyManager.EXTRA_ALL_SENSORS;
import static android.hardware.SensorPrivacyManager.EXTRA_SENSOR;
@@ -77,6 +78,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.res.Configuration;
import android.graphics.drawable.Icon;
import android.hardware.ISensorPrivacyListener;
@@ -155,6 +157,7 @@ public final class SensorPrivacyService extends SystemService {
private final AppOpsManager mAppOpsManager;
private final AppOpsManagerInternal mAppOpsManagerInternal;
private final TelephonyManager mTelephonyManager;
+ private final PackageManagerInternal mPackageManagerInternal;
private CameraPrivacyLightController mCameraPrivacyLightController;
@@ -178,6 +181,7 @@ public final class SensorPrivacyService extends SystemService {
mActivityManagerInternal = getLocalService(ActivityManagerInternal.class);
mActivityTaskManager = context.getSystemService(ActivityTaskManager.class);
mTelephonyManager = context.getSystemService(TelephonyManager.class);
+ mPackageManagerInternal = getLocalService(PackageManagerInternal.class);
mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl();
}
@@ -828,6 +832,12 @@ public final class SensorPrivacyService extends SystemService {
* sensor privacy.
*/
private void enforceObserveSensorPrivacyPermission() {
+ String systemUIPackage = mContext.getString(R.string.config_systemUi);
+ if (Binder.getCallingUid() == mPackageManagerInternal
+ .getPackageUid(systemUIPackage, MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM)) {
+ // b/221782106, possible race condition with role grant might bootloop device.
+ return;
+ }
enforcePermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY,
"Observing sensor privacy changes requires the following permission: "
+ android.Manifest.permission.OBSERVE_SENSOR_PRIVACY);
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java
index c0ab65a3215c..05d92beed11f 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandler.java
@@ -17,7 +17,6 @@
package com.android.server.soundtrigger_middleware;
import android.annotation.NonNull;
-import android.media.permission.SafeCloseable;
import android.media.soundtrigger.ModelParameterRange;
import android.media.soundtrigger.PhraseRecognitionEvent;
import android.media.soundtrigger.PhraseSoundModel;
@@ -30,6 +29,7 @@ import android.media.soundtrigger.Status;
import android.os.IBinder;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
@@ -63,18 +63,24 @@ import java.util.concurrent.ConcurrentHashMap;
*/
public class SoundTriggerHalConcurrentCaptureHandler implements ISoundTriggerHal,
ICaptureStateNotifier.Listener {
- private final @NonNull ISoundTriggerHal mDelegate;
+ @NonNull private final ISoundTriggerHal mDelegate;
private GlobalCallback mGlobalCallback;
+ /**
+ * This lock must be held to synchronize forward calls (start/stop/onCaptureStateChange) that
+ * update the mActiveModels set and mCaptureState.
+ * It must not be locked in HAL callbacks to avoid deadlocks.
+ */
+ @NonNull private final Object mStartStopLock = new Object();
/**
* Information about a model that is currently loaded. This is needed in order to be able to
* send abort events to its designated callback.
*/
private static class LoadedModel {
- final int type;
- final @NonNull ModelCallback callback;
+ public final int type;
+ @NonNull public final ModelCallback callback;
- private LoadedModel(int type, @NonNull ModelCallback callback) {
+ LoadedModel(int type, @NonNull ModelCallback callback) {
this.type = type;
this.callback = callback;
}
@@ -83,19 +89,19 @@ public class SoundTriggerHalConcurrentCaptureHandler implements ISoundTriggerHal
/**
* This map holds the model type for every model that is loaded.
*/
- private final @NonNull Map<Integer, LoadedModel> mLoadedModels = new ConcurrentHashMap<>();
+ @NonNull private final Map<Integer, LoadedModel> mLoadedModels = new ConcurrentHashMap<>();
/**
* A set of all models that are currently active.
* We use this in order to know which models to stop in case of external capture.
* Used as a lock to synchronize operations that effect activity.
*/
- private final @NonNull Set<Integer> mActiveModels = new HashSet<>();
+ @NonNull private final Set<Integer> mActiveModels = new HashSet<>();
/**
* Notifier for changes in capture state.
*/
- private final @NonNull ICaptureStateNotifier mNotifier;
+ @NonNull private final ICaptureStateNotifier mNotifier;
/**
* Whether capture is active.
@@ -106,10 +112,10 @@ public class SoundTriggerHalConcurrentCaptureHandler implements ISoundTriggerHal
* Since we're wrapping the death recipient, we need to keep a translation map for unlinking.
* Key is the client recipient, value is the wrapper.
*/
- private final @NonNull Map<IBinder.DeathRecipient, IBinder.DeathRecipient>
+ @NonNull private final Map<IBinder.DeathRecipient, IBinder.DeathRecipient>
mDeathRecipientMap = new ConcurrentHashMap<>();
- private final @NonNull CallbackThread mCallbackThread = new CallbackThread();
+ @NonNull private final CallbackThread mCallbackThread = new CallbackThread();
public SoundTriggerHalConcurrentCaptureHandler(
@NonNull ISoundTriggerHal delegate,
@@ -122,20 +128,28 @@ public class SoundTriggerHalConcurrentCaptureHandler implements ISoundTriggerHal
@Override
public void startRecognition(int modelHandle, int deviceHandle, int ioHandle,
RecognitionConfig config) {
- synchronized (mActiveModels) {
- if (mCaptureState) {
- throw new RecoverableException(Status.RESOURCE_CONTENTION);
+ synchronized (mStartStopLock) {
+ synchronized (mActiveModels) {
+ if (mCaptureState) {
+ throw new RecoverableException(Status.RESOURCE_CONTENTION);
+ }
+ mDelegate.startRecognition(modelHandle, deviceHandle, ioHandle, config);
+ mActiveModels.add(modelHandle);
}
- mDelegate.startRecognition(modelHandle, deviceHandle, ioHandle, config);
- mActiveModels.add(modelHandle);
}
}
@Override
public void stopRecognition(int modelHandle) {
- synchronized (mActiveModels) {
- mDelegate.stopRecognition(modelHandle);
- mActiveModels.remove(modelHandle);
+ synchronized (mStartStopLock) {
+ boolean wasActive;
+ synchronized (mActiveModels) {
+ wasActive = mActiveModels.remove(modelHandle);
+ }
+ if (wasActive) {
+ // Must be done outside of the lock, since it may trigger synchronous callbacks.
+ mDelegate.stopRecognition(modelHandle);
+ }
}
// Block until all previous events are delivered. Since this is potentially blocking on
// upward calls, it must be done outside the lock.
@@ -144,27 +158,38 @@ public class SoundTriggerHalConcurrentCaptureHandler implements ISoundTriggerHal
@Override
public void onCaptureStateChange(boolean active) {
- synchronized (mActiveModels) {
+ synchronized (mStartStopLock) {
if (active) {
- // Abort all active models. This must be done as one transaction to the event
- // thread, in order to be able to dedupe events before they are delivered.
- try (SafeCloseable ignored = mCallbackThread.stallReader()) {
- for (int modelHandle : mActiveModels) {
- mDelegate.stopRecognition(modelHandle);
- LoadedModel model = mLoadedModels.get(modelHandle);
- // An abort event must be the last one for its model.
- mCallbackThread.pushWithDedupe(modelHandle, true,
- () -> notifyAbort(modelHandle, model));
- }
- }
+ abortAllActiveModels();
} else {
- mGlobalCallback.onResourcesAvailable();
+ if (mGlobalCallback != null) {
+ mGlobalCallback.onResourcesAvailable();
+ }
}
-
mCaptureState = active;
}
}
+ private void abortAllActiveModels() {
+ while (true) {
+ int toStop;
+ synchronized (mActiveModels) {
+ Iterator<Integer> iterator = mActiveModels.iterator();
+ if (!iterator.hasNext()) {
+ return;
+ }
+ toStop = iterator.next();
+ mActiveModels.remove(toStop);
+ }
+ // Invoke stop outside of the lock.
+ mDelegate.stopRecognition(toStop);
+
+ LoadedModel model = mLoadedModels.get(toStop);
+ // Queue an abort event (no need to flush).
+ mCallbackThread.push(() -> notifyAbort(toStop, model));
+ }
+ }
+
@Override
public int loadSoundModel(SoundModel soundModel, ModelCallback callback) {
int handle = mDelegate.loadSoundModel(soundModel, new CallbackWrapper(callback));
@@ -188,23 +213,13 @@ public class SoundTriggerHalConcurrentCaptureHandler implements ISoundTriggerHal
@Override
public void registerCallback(GlobalCallback callback) {
- mGlobalCallback = new GlobalCallback() {
- @Override
- public void onResourcesAvailable() {
- mCallbackThread.push(callback::onResourcesAvailable);
- }
- };
+ mGlobalCallback = () -> mCallbackThread.push(callback::onResourcesAvailable);
mDelegate.registerCallback(mGlobalCallback);
}
@Override
public void linkToDeath(IBinder.DeathRecipient recipient) {
- IBinder.DeathRecipient wrapper = new IBinder.DeathRecipient() {
- @Override
- public void binderDied() {
- mCallbackThread.push(() -> recipient.binderDied());
- }
- };
+ IBinder.DeathRecipient wrapper = () -> mCallbackThread.push(recipient::binderDied);
mDelegate.linkToDeath(wrapper);
mDeathRecipientMap.put(recipient, wrapper);
}
@@ -215,7 +230,7 @@ public class SoundTriggerHalConcurrentCaptureHandler implements ISoundTriggerHal
}
private class CallbackWrapper implements ISoundTriggerHal.ModelCallback {
- private final @NonNull ISoundTriggerHal.ModelCallback mDelegateCallback;
+ @NonNull private final ISoundTriggerHal.ModelCallback mDelegateCallback;
private CallbackWrapper(@NonNull ModelCallback delegateCallback) {
mDelegateCallback = delegateCallback;
@@ -223,18 +238,36 @@ public class SoundTriggerHalConcurrentCaptureHandler implements ISoundTriggerHal
@Override
public void recognitionCallback(int modelHandle, RecognitionEvent event) {
- // A recognition event must be the last one for its model, unless it is a forced one
- // (those leave the model active).
- mCallbackThread.pushWithDedupe(modelHandle, !event.recognitionStillActive,
- () -> mDelegateCallback.recognitionCallback(modelHandle, event));
+ synchronized (mActiveModels) {
+ if (!mActiveModels.contains(modelHandle)) {
+ // Discard the event.
+ return;
+ }
+ if (!event.recognitionStillActive) {
+ mActiveModels.remove(modelHandle);
+ }
+ // A recognition event must be the last one for its model, unless it indicates that
+ // recognition is still active.
+ mCallbackThread.push(
+ () -> mDelegateCallback.recognitionCallback(modelHandle, event));
+ }
}
@Override
public void phraseRecognitionCallback(int modelHandle, PhraseRecognitionEvent event) {
- // A recognition event must be the last one for its model, unless it is a forced one
- // (those leave the model active).
- mCallbackThread.pushWithDedupe(modelHandle, !event.common.recognitionStillActive,
- () -> mDelegateCallback.phraseRecognitionCallback(modelHandle, event));
+ synchronized (mActiveModels) {
+ if (!mActiveModels.contains(modelHandle)) {
+ // Discard the event.
+ return;
+ }
+ if (!event.common.recognitionStillActive) {
+ mActiveModels.remove(modelHandle);
+ }
+ // A recognition event must be the last one for its model, unless it indicates that
+ // recognition is still active.
+ mCallbackThread.push(
+ () -> mDelegateCallback.phraseRecognitionCallback(modelHandle, event));
+ }
}
@Override
@@ -254,36 +287,12 @@ public class SoundTriggerHalConcurrentCaptureHandler implements ISoundTriggerHal
* <ul>
* <li>Events are processed on a separate thread than the thread that pushed them, in the order
* they were pushed.
- * <li>Events can be deduped upon entry to the queue. This is achieved as follows:
- * <ul>
- * <li>Temporarily stall the reader via {@link #stallReader()}.
- * <li>Within this scope, push as many events as needed via
- * {@link #pushWithDedupe(int, boolean, Runnable)}.
- * If an event with the same model handle as the one being pushed is already in the queue
- * and has been marked as "lastForModel", the new event will be discarded before entering
- * the queue.
- * <li>Finally, un-stall the reader by existing the scope.
- * <li>Events that do not require deduping can be pushed via {@link #push(Runnable)}.
- * </ul>
* <li>Events can be flushed via {@link #flush()}. This will block until all events pushed prior
* to this call have been fully processed.
* </ul>
*/
private static class CallbackThread {
- private static class Entry {
- final boolean lastForModel;
- final int modelHandle;
- final Runnable runnable;
-
- private Entry(boolean lastForModel, int modelHandle, Runnable runnable) {
- this.lastForModel = lastForModel;
- this.modelHandle = modelHandle;
- this.runnable = runnable;
- }
- }
-
- private boolean mStallReader = false;
- private final Queue<Entry> mList = new LinkedList<>();
+ private final Queue<Runnable> mList = new LinkedList<>();
private int mPushCount = 0;
private int mProcessedCount = 0;
@@ -312,23 +321,11 @@ public class SoundTriggerHalConcurrentCaptureHandler implements ISoundTriggerHal
* @param runnable The runnable to push.
*/
void push(Runnable runnable) {
- pushEntry(new Entry(false, 0, runnable), false);
- }
-
-
- /**
- * Push a new runnable to the queue, with deduping.
- * If an entry with the same model handle is already in the queue and was designated as
- * last for model, this one will be discarded.
- *
- * @param modelHandle The model handle, used for deduping purposes.
- * @param lastForModel If true, this entry will be considered the last one for this model
- * and any subsequence calls for this handle (whether lastForModel or
- * not) will be discarded while this entry is in the queue.
- * @param runnable The runnable to push.
- */
- void pushWithDedupe(int modelHandle, boolean lastForModel, Runnable runnable) {
- pushEntry(new Entry(lastForModel, modelHandle, runnable), true);
+ synchronized (mList) {
+ mList.add(runnable);
+ mPushCount++;
+ mList.notifyAll();
+ }
}
/**
@@ -346,45 +343,15 @@ public class SoundTriggerHalConcurrentCaptureHandler implements ISoundTriggerHal
}
}
- /**
- * Creates a scope (using a try-with-resources block), within which events that are pushed
- * remain queued and processed. This is useful in order to utilize deduping.
- */
- SafeCloseable stallReader() {
- synchronized (mList) {
- mStallReader = true;
- return () -> {
- synchronized (mList) {
- mStallReader = false;
- mList.notifyAll();
- }
- };
- }
- }
-
- private void pushEntry(Entry entry, boolean dedupe) {
- synchronized (mList) {
- if (dedupe) {
- for (Entry existing : mList) {
- if (existing.lastForModel && existing.modelHandle == entry.modelHandle) {
- return;
- }
- }
- }
- mList.add(entry);
- mPushCount++;
- mList.notifyAll();
- }
- }
-
private Runnable pop() throws InterruptedException {
synchronized (mList) {
- while (mStallReader || mList.isEmpty()) {
+ while (mList.isEmpty()) {
mList.wait();
}
- return mList.remove().runnable;
+ return mList.remove();
}
}
+
}
/** Notify the client that recognition has been aborted. */
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index 8ecc51b3087c..3e364314e10f 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -66,6 +66,7 @@ final class Vibration {
IGNORED_FOR_ONGOING,
IGNORED_FOR_POWER,
IGNORED_FOR_RINGER_MODE,
+ IGNORED_FOR_RINGTONE,
IGNORED_FOR_SETTINGS,
IGNORED_SUPERSEDED,
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index bf3298558cd6..d7341cb37685 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -733,6 +733,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
+ attrs);
}
break;
+ case IGNORED_FOR_RINGTONE:
+ if (DEBUG) {
+ Slog.d(TAG, "Ignoring incoming vibration in favor of ringtone vibration");
+ }
+ break;
+
default:
if (DEBUG) {
Slog.d(TAG, "Vibration for uid=" + uid + " and with attrs=" + attrs
@@ -809,6 +815,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
return Vibration.Status.IGNORED_FOR_ALARM;
}
+ if (currentVibration.attrs.getUsage() == VibrationAttributes.USAGE_RINGTONE) {
+ return Vibration.Status.IGNORED_FOR_RINGTONE;
+ }
+
if (currentVibration.isRepeating()) {
return Vibration.Status.IGNORED_FOR_ONGOING;
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index eb5ca9c2f43b..95de040551b1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -897,6 +897,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
proc.getThread(), r.token);
final boolean isTransitionForward = r.isTransitionForward();
+ final IBinder fragmentToken = r.getTaskFragment().getFragmentToken();
clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent),
System.identityHashCode(r), r.info,
// TODO: Have this take the merged configuration instead of separate global
@@ -907,7 +908,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
proc.getReportedProcState(), r.getSavedState(), r.getPersistentSavedState(),
results, newIntents, r.takeOptions(), isTransitionForward,
proc.createProfilerInfoIfNeeded(), r.assistToken, activityClientController,
- r.shareableActivityToken, r.getLaunchedFromBubble()));
+ r.shareableActivityToken, r.getLaunchedFromBubble(), fragmentToken));
// Set desired final state.
final ActivityLifecycleItem lifecycleItem;
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 8d1425d17d47..67dd89ee295c 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -29,6 +29,7 @@ import android.graphics.PointF;
import android.os.Debug;
import android.os.IBinder;
import android.util.Slog;
+import android.view.Display;
import android.view.InputApplicationHandle;
import android.view.KeyEvent;
import android.view.SurfaceControl;
@@ -194,6 +195,9 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal
int firstExternalDisplayId = DEFAULT_DISPLAY;
for (int i = mService.mRoot.mChildren.size() - 1; i >= 0; --i) {
final DisplayContent displayContent = mService.mRoot.mChildren.get(i);
+ if (displayContent.getDisplayInfo().state == Display.STATE_OFF) {
+ continue;
+ }
// Heuristic solution here. Currently when "Freeform windows" developer option is
// enabled we automatically put secondary displays in freeform mode and emulating
// "desktop mode". It also makes sense to show the pointer on the same display.
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 00f7e6362be9..65062e576d55 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -249,6 +249,8 @@ final class InputMonitor {
inputWindowHandle.setPaused(w.mActivityRecord != null && w.mActivityRecord.paused);
inputWindowHandle.setWindowToken(w.mClient);
+ inputWindowHandle.setName(w.getName());
+
// Update layout params flags to force the window to be not touch modal. We do this to
// restrict the window's touchable region to the task even if it requests touches outside
// its window bounds. An example is a dialog in primary split should get touches outside its
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
index 65dca86d0259..83be73a47eb4 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
@@ -176,13 +176,24 @@ class SurfaceAnimationRunner {
t.addTransactionCommittedListener(Runnable::run, () -> {
final WindowAnimationSpec animationSpec = a.asWindowAnimationSpec();
+ final Transaction edgeExtensionCreationTransaction = new Transaction();
edgeExtendWindow(animationLeash,
animationSpec.getRootTaskBounds(), animationSpec.getAnimation(),
- mFrameTransaction);
+ edgeExtensionCreationTransaction);
synchronized (mLock) {
// only run if animation is not yet canceled by this point
if (mPreProcessingAnimations.get(animationLeash) == runningAnim) {
+ // In the case the animation is cancelled, edge extensions are removed
+ // onAnimationLeashLost which is called before onAnimationCancelled.
+ // So we need to check if the edge extensions have already been removed
+ // or not, and if so we don't want to apply the transaction.
+ synchronized (mEdgeExtensionLock) {
+ if (!mEdgeExtensions.isEmpty()) {
+ edgeExtensionCreationTransaction.apply();
+ }
+ }
+
mPreProcessingAnimations.remove(animationLeash);
mPendingAnimations.put(animationLeash, runningAnim);
if (!mAnimationStartDeferred) {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 3a458fdc75bb..900963e29bd6 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2262,7 +2262,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
mFragmentToken,
mRemoteToken.toWindowContainerToken(),
getConfiguration(),
- getChildCount() == 0,
+ runningActivityCount[0] == 0,
runningActivityCount[0],
isVisible(),
childActivities,
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 8546e8002602..51d68bc0177a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -6125,14 +6125,21 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
applyHere = true;
}
- for (int i = mDrawHandlers.size() - 1; i >= 0; i--) {
- DrawHandler h = mDrawHandlers.get(i);
+ final List<DrawHandler> handlersToRemove = new ArrayList<>();
+ // Iterate forwards to ensure we process in the same order
+ // we added.
+ for (int i = 0; i < mDrawHandlers.size(); i++) {
+ final DrawHandler h = mDrawHandlers.get(i);
if (h.mSeqId <= seqId) {
h.mConsumer.accept(t);
- mDrawHandlers.remove(h);
+ handlersToRemove.add(h);
hadHandlers = true;
}
}
+ for (int i = 0; i < handlersToRemove.size(); i++) {
+ final DrawHandler h = handlersToRemove.get(i);
+ mDrawHandlers.remove(h);
+ }
if (hadHandlers) {
mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this);
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 31b557949e36..fa5e450c687a 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -106,7 +106,6 @@ static struct {
jmethodID interceptMotionBeforeQueueingNonInteractive;
jmethodID interceptKeyBeforeDispatching;
jmethodID dispatchUnhandledKey;
- jmethodID checkInjectEventsPermission;
jmethodID onPointerDownOutsideFocus;
jmethodID getVirtualKeyQuietTimeMillis;
jmethodID getExcludedDeviceNames;
@@ -131,6 +130,11 @@ static struct {
static struct {
jclass clazz;
+ jfieldID mPtr;
+} gNativeInputManagerServiceImpl;
+
+static struct {
+ jclass clazz;
} gInputDeviceClassInfo;
static struct {
@@ -260,17 +264,16 @@ public:
void setDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray);
- base::Result<std::unique_ptr<InputChannel>> createInputChannel(JNIEnv* env,
- const std::string& name);
- base::Result<std::unique_ptr<InputChannel>> createInputMonitor(JNIEnv* env, int32_t displayId,
+ base::Result<std::unique_ptr<InputChannel>> createInputChannel(const std::string& name);
+ base::Result<std::unique_ptr<InputChannel>> createInputMonitor(int32_t displayId,
const std::string& name,
int32_t pid);
- status_t removeInputChannel(JNIEnv* env, const sp<IBinder>& connectionToken);
+ status_t removeInputChannel(const sp<IBinder>& connectionToken);
status_t pilferPointers(const sp<IBinder>& token);
void displayRemoved(JNIEnv* env, int32_t displayId);
void setFocusedApplication(JNIEnv* env, int32_t displayId, jobject applicationHandleObj);
- void setFocusedDisplay(JNIEnv* env, int32_t displayId);
+ void setFocusedDisplay(int32_t displayId);
void setInputDispatchMode(bool enabled, bool frozen);
void setSystemUiLightsOut(bool lightsOut);
void setPointerSpeed(int32_t speed);
@@ -329,7 +332,6 @@ public:
bool dispatchUnhandledKey(const sp<IBinder>& token, const KeyEvent* keyEvent,
uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) override;
void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override;
- bool checkInjectEventsPermissionNonReentrant(int32_t injectorPid, int32_t injectorUid) override;
void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) override;
void setPointerCapture(const PointerCaptureRequest& request) override;
void notifyDropWindow(const sp<IBinder>& token, float x, float y) override;
@@ -512,19 +514,18 @@ void NativeInputManager::setDisplayViewports(JNIEnv* env, jobjectArray viewportO
}
base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputChannel(
- JNIEnv* /* env */, const std::string& name) {
+ const std::string& name) {
ATRACE_CALL();
return mInputManager->getDispatcher().createInputChannel(name);
}
base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputMonitor(
- JNIEnv* /* env */, int32_t displayId, const std::string& name, int32_t pid) {
+ int32_t displayId, const std::string& name, int32_t pid) {
ATRACE_CALL();
return mInputManager->getDispatcher().createInputMonitor(displayId, name, pid);
}
-status_t NativeInputManager::removeInputChannel(JNIEnv* /* env */,
- const sp<IBinder>& connectionToken) {
+status_t NativeInputManager::removeInputChannel(const sp<IBinder>& connectionToken) {
ATRACE_CALL();
return mInputManager->getDispatcher().removeInputChannel(connectionToken);
}
@@ -1002,7 +1003,7 @@ void NativeInputManager::setFocusedApplication(JNIEnv* env, int32_t displayId,
mInputManager->getDispatcher().setFocusedApplication(displayId, applicationHandle);
}
-void NativeInputManager::setFocusedDisplay(JNIEnv* env, int32_t displayId) {
+void NativeInputManager::setFocusedDisplay(int32_t displayId) {
mInputManager->getDispatcher().setFocusedDisplay(displayId);
}
@@ -1368,18 +1369,6 @@ void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType,
android_server_PowerManagerService_userActivity(eventTime, eventType, displayId);
}
-bool NativeInputManager::checkInjectEventsPermissionNonReentrant(
- int32_t injectorPid, int32_t injectorUid) {
- ATRACE_CALL();
- JNIEnv* env = jniEnv();
- jboolean result = env->CallBooleanMethod(mServiceObj,
- gServiceClassInfo.checkInjectEventsPermission, injectorPid, injectorUid);
- if (checkAndClearExceptionFromCallback(env, "checkInjectEventsPermission")) {
- result = false;
- }
- return result;
-}
-
void NativeInputManager::onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) {
ATRACE_CALL();
JNIEnv* env = jniEnv();
@@ -1504,6 +1493,11 @@ void NativeInputManager::notifyPointerDisplayIdChanged() {
// ----------------------------------------------------------------------------
+static NativeInputManager* getNativeInputManager(JNIEnv* env, jobject clazz) {
+ return reinterpret_cast<NativeInputManager*>(
+ env->GetLongField(clazz, gNativeInputManagerServiceImpl.mPtr));
+}
+
static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
@@ -1518,8 +1512,8 @@ static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
return reinterpret_cast<jlong>(im);
}
-static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeStart(JNIEnv* env, jobject nativeImplObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
status_t result = im->getInputManager()->start();
if (result) {
@@ -1527,39 +1521,39 @@ static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {
}
}
-static void nativeSetDisplayViewports(JNIEnv* env, jclass /* clazz */, jlong ptr,
- jobjectArray viewportObjArray) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetDisplayViewports(JNIEnv* env, jobject nativeImplObj,
+ jobjectArray viewportObjArray) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->setDisplayViewports(env, viewportObjArray);
}
-static jint nativeGetScanCodeState(JNIEnv* /* env */, jclass /* clazz */,
- jlong ptr, jint deviceId, jint sourceMask, jint scanCode) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jint nativeGetScanCodeState(JNIEnv* env, jobject nativeImplObj, jint deviceId,
+ jint sourceMask, jint scanCode) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
return (jint)im->getInputManager()->getReader().getScanCodeState(deviceId, uint32_t(sourceMask),
scanCode);
}
-static jint nativeGetKeyCodeState(JNIEnv* /* env */, jclass /* clazz */,
- jlong ptr, jint deviceId, jint sourceMask, jint keyCode) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jint nativeGetKeyCodeState(JNIEnv* env, jobject nativeImplObj, jint deviceId,
+ jint sourceMask, jint keyCode) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
return (jint)im->getInputManager()->getReader().getKeyCodeState(deviceId, uint32_t(sourceMask),
keyCode);
}
-static jint nativeGetSwitchState(JNIEnv* /* env */, jclass /* clazz */,
- jlong ptr, jint deviceId, jint sourceMask, jint sw) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jint nativeGetSwitchState(JNIEnv* env, jobject nativeImplObj, jint deviceId, jint sourceMask,
+ jint sw) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
return (jint)im->getInputManager()->getReader().getSwitchState(deviceId, uint32_t(sourceMask),
sw);
}
-static jboolean nativeHasKeys(JNIEnv* env, jclass /* clazz */,
- jlong ptr, jint deviceId, jint sourceMask, jintArray keyCodes, jbooleanArray outFlags) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jboolean nativeHasKeys(JNIEnv* env, jobject nativeImplObj, jint deviceId, jint sourceMask,
+ jintArray keyCodes, jbooleanArray outFlags) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
int32_t* codes = env->GetIntArrayElements(keyCodes, nullptr);
uint8_t* flags = env->GetBooleanArrayElements(outFlags, nullptr);
@@ -1581,9 +1575,9 @@ static jboolean nativeHasKeys(JNIEnv* env, jclass /* clazz */,
return result;
}
-static jint nativeGetKeyCodeForKeyLocation(JNIEnv* env, jclass /* clazz */, jlong ptr,
- jint deviceId, jint locationKeyCode) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jint nativeGetKeyCodeForKeyLocation(JNIEnv* env, jobject nativeImplObj, jint deviceId,
+ jint locationKeyCode) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
return (jint)im->getInputManager()->getReader().getKeyCodeForKeyLocation(deviceId,
locationKeyCode);
}
@@ -1596,17 +1590,16 @@ static void handleInputChannelDisposed(JNIEnv* env, jobject /* inputChannelObj *
ALOGW("Input channel object '%s' was disposed without first being removed with "
"the input manager!",
inputChannel->getName().c_str());
- im->removeInputChannel(env, inputChannel->getConnectionToken());
+ im->removeInputChannel(inputChannel->getConnectionToken());
}
-static jobject nativeCreateInputChannel(JNIEnv* env, jclass /* clazz */, jlong ptr,
- jstring nameObj) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jobject nativeCreateInputChannel(JNIEnv* env, jobject nativeImplObj, jstring nameObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
ScopedUtfChars nameChars(env, nameObj);
std::string name = nameChars.c_str();
- base::Result<std::unique_ptr<InputChannel>> inputChannel = im->createInputChannel(env, name);
+ base::Result<std::unique_ptr<InputChannel>> inputChannel = im->createInputChannel(name);
if (!inputChannel.ok()) {
std::string message = inputChannel.error().message();
@@ -1626,9 +1619,9 @@ static jobject nativeCreateInputChannel(JNIEnv* env, jclass /* clazz */, jlong p
return inputChannelObj;
}
-static jobject nativeCreateInputMonitor(JNIEnv* env, jclass /* clazz */, jlong ptr, jint displayId,
+static jobject nativeCreateInputMonitor(JNIEnv* env, jobject nativeImplObj, jint displayId,
jstring nameObj, jint pid) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
if (displayId == ADISPLAY_ID_NONE) {
std::string message = "InputChannel used as a monitor must be associated with a display";
@@ -1640,7 +1633,7 @@ static jobject nativeCreateInputMonitor(JNIEnv* env, jclass /* clazz */, jlong p
std::string name = nameChars.c_str();
base::Result<std::unique_ptr<InputChannel>> inputChannel =
- im->createInputMonitor(env, displayId, name, pid);
+ im->createInputMonitor(displayId, name, pid);
if (!inputChannel.ok()) {
std::string message = inputChannel.error().message();
@@ -1657,11 +1650,11 @@ static jobject nativeCreateInputMonitor(JNIEnv* env, jclass /* clazz */, jlong p
return inputChannelObj;
}
-static void nativeRemoveInputChannel(JNIEnv* env, jclass /* clazz */, jlong ptr, jobject tokenObj) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeRemoveInputChannel(JNIEnv* env, jobject nativeImplObj, jobject tokenObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
sp<IBinder> token = ibinderForJavaObject(env, tokenObj);
- status_t status = im->removeInputChannel(env, token);
+ status_t status = im->removeInputChannel(token);
if (status && status != BAD_VALUE) { // ignore already removed channel
std::string message;
message += StringPrintf("Failed to remove input channel. status=%d", status);
@@ -1669,48 +1662,46 @@ static void nativeRemoveInputChannel(JNIEnv* env, jclass /* clazz */, jlong ptr,
}
}
-static void nativePilferPointers(JNIEnv* env, jclass /* clazz */, jlong ptr, jobject tokenObj) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativePilferPointers(JNIEnv* env, jobject nativeImplObj, jobject tokenObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
sp<IBinder> token = ibinderForJavaObject(env, tokenObj);
im->pilferPointers(token);
}
-static void nativeSetInputFilterEnabled(JNIEnv* /* env */, jclass /* clazz */,
- jlong ptr, jboolean enabled) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetInputFilterEnabled(JNIEnv* env, jobject nativeImplObj, jboolean enabled) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getDispatcher().setInputFilterEnabled(enabled);
}
-static jboolean nativeSetInTouchMode(JNIEnv* /* env */, jclass /* clazz */, jlong ptr,
- jboolean inTouchMode, jint pid, jint uid,
- jboolean hasPermission) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jboolean nativeSetInTouchMode(JNIEnv* env, jobject nativeImplObj, jboolean inTouchMode,
+ jint pid, jint uid, jboolean hasPermission) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
return im->getInputManager()->getDispatcher().setInTouchMode(inTouchMode, pid, uid,
hasPermission);
}
-static void nativeSetMaximumObscuringOpacityForTouch(JNIEnv* /* env */, jclass /* clazz */,
- jlong ptr, jfloat opacity) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetMaximumObscuringOpacityForTouch(JNIEnv* env, jobject nativeImplObj,
+ jfloat opacity) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getDispatcher().setMaximumObscuringOpacityForTouch(opacity);
}
-static void nativeSetBlockUntrustedTouchesMode(JNIEnv* env, jclass /* clazz */, jlong ptr,
- jint mode) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetBlockUntrustedTouchesMode(JNIEnv* env, jobject nativeImplObj, jint mode) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getDispatcher().setBlockUntrustedTouchesMode(
static_cast<BlockUntrustedTouchesMode>(mode));
}
-static jint nativeInjectInputEvent(JNIEnv* env, jclass /* clazz */,
- jlong ptr, jobject inputEventObj, jint injectorPid, jint injectorUid,
- jint syncMode, jint timeoutMillis, jint policyFlags) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jint nativeInjectInputEvent(JNIEnv* env, jobject nativeImplObj, jobject inputEventObj,
+ jboolean injectIntoUid, jint uid, jint syncMode,
+ jint timeoutMillis, jint policyFlags) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ const std::optional<int32_t> targetUid = injectIntoUid ? std::make_optional(uid) : std::nullopt;
// static_cast is safe because the value was already checked at the Java layer
InputEventInjectionSync mode = static_cast<InputEventInjectionSync>(syncMode);
@@ -1723,8 +1714,7 @@ static jint nativeInjectInputEvent(JNIEnv* env, jclass /* clazz */,
}
const InputEventInjectionResult result =
- im->getInputManager()->getDispatcher().injectInputEvent(&keyEvent, injectorPid,
- injectorUid, mode,
+ im->getInputManager()->getDispatcher().injectInputEvent(&keyEvent, targetUid, mode,
std::chrono::milliseconds(
timeoutMillis),
uint32_t(policyFlags));
@@ -1737,8 +1727,8 @@ static jint nativeInjectInputEvent(JNIEnv* env, jclass /* clazz */,
}
const InputEventInjectionResult result =
- im->getInputManager()->getDispatcher().injectInputEvent(motionEvent, injectorPid,
- injectorUid, mode,
+ im->getInputManager()->getDispatcher().injectInputEvent(motionEvent, targetUid,
+ mode,
std::chrono::milliseconds(
timeoutMillis),
uint32_t(policyFlags));
@@ -1749,9 +1739,8 @@ static jint nativeInjectInputEvent(JNIEnv* env, jclass /* clazz */,
}
}
-static jobject nativeVerifyInputEvent(JNIEnv* env, jclass /* clazz */, jlong ptr,
- jobject inputEventObj) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jobject nativeVerifyInputEvent(JNIEnv* env, jobject nativeImplObj, jobject inputEventObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
if (env->IsInstanceOf(inputEventObj, gKeyEventClassInfo.clazz)) {
KeyEvent keyEvent;
@@ -1792,56 +1781,53 @@ static jobject nativeVerifyInputEvent(JNIEnv* env, jclass /* clazz */, jlong ptr
}
}
-static void nativeToggleCapsLock(JNIEnv* env, jclass /* clazz */,
- jlong ptr, jint deviceId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeToggleCapsLock(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getReader().toggleCapsLockState(deviceId);
}
-static void nativeDisplayRemoved(JNIEnv* env, jclass /* clazz */, jlong ptr, jint displayId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeDisplayRemoved(JNIEnv* env, jobject nativeImplObj, jint displayId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->displayRemoved(env, displayId);
}
-static void nativeSetFocusedApplication(JNIEnv* env, jclass /* clazz */,
- jlong ptr, jint displayId, jobject applicationHandleObj) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetFocusedApplication(JNIEnv* env, jobject nativeImplObj, jint displayId,
+ jobject applicationHandleObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->setFocusedApplication(env, displayId, applicationHandleObj);
}
-static void nativeSetFocusedDisplay(JNIEnv* env, jclass /* clazz */,
- jlong ptr, jint displayId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetFocusedDisplay(JNIEnv* env, jobject nativeImplObj, jint displayId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- im->setFocusedDisplay(env, displayId);
+ im->setFocusedDisplay(displayId);
}
-static void nativeRequestPointerCapture(JNIEnv* env, jclass /* clazz */, jlong ptr,
- jobject tokenObj, jboolean enabled) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeRequestPointerCapture(JNIEnv* env, jobject nativeImplObj, jobject tokenObj,
+ jboolean enabled) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
sp<IBinder> windowToken = ibinderForJavaObject(env, tokenObj);
im->requestPointerCapture(windowToken, enabled);
}
-static void nativeSetInputDispatchMode(JNIEnv* /* env */,
- jclass /* clazz */, jlong ptr, jboolean enabled, jboolean frozen) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetInputDispatchMode(JNIEnv* env, jobject nativeImplObj, jboolean enabled,
+ jboolean frozen) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->setInputDispatchMode(enabled, frozen);
}
-static void nativeSetSystemUiLightsOut(JNIEnv* /* env */, jclass /* clazz */, jlong ptr,
- jboolean lightsOut) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetSystemUiLightsOut(JNIEnv* env, jobject nativeImplObj, jboolean lightsOut) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->setSystemUiLightsOut(lightsOut);
}
-static jboolean nativeTransferTouchFocus(JNIEnv* env, jclass /* clazz */, jlong ptr,
+static jboolean nativeTransferTouchFocus(JNIEnv* env, jobject nativeImplObj,
jobject fromChannelTokenObj, jobject toChannelTokenObj,
jboolean isDragDrop) {
if (fromChannelTokenObj == nullptr || toChannelTokenObj == nullptr) {
@@ -1851,7 +1837,7 @@ static jboolean nativeTransferTouchFocus(JNIEnv* env, jclass /* clazz */, jlong
sp<IBinder> fromChannelToken = ibinderForJavaObject(env, fromChannelTokenObj);
sp<IBinder> toChannelToken = ibinderForJavaObject(env, toChannelTokenObj);
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
if (im->getInputManager()->getDispatcher().transferTouchFocus(fromChannelToken, toChannelToken,
isDragDrop)) {
return JNI_TRUE;
@@ -1860,11 +1846,11 @@ static jboolean nativeTransferTouchFocus(JNIEnv* env, jclass /* clazz */, jlong
}
}
-static jboolean nativeTransferTouch(JNIEnv* env, jclass /* clazz */, jlong ptr,
+static jboolean nativeTransferTouch(JNIEnv* env, jobject nativeImplObj,
jobject destChannelTokenObj) {
sp<IBinder> destChannelToken = ibinderForJavaObject(env, destChannelTokenObj);
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
if (im->getInputManager()->getDispatcher().transferTouch(destChannelToken)) {
return JNI_TRUE;
} else {
@@ -1872,42 +1858,39 @@ static jboolean nativeTransferTouch(JNIEnv* env, jclass /* clazz */, jlong ptr,
}
}
-static void nativeSetPointerSpeed(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jint speed) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetPointerSpeed(JNIEnv* env, jobject nativeImplObj, jint speed) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->setPointerSpeed(speed);
}
-static void nativeSetPointerAcceleration(JNIEnv* /* env */, jclass /* clazz */, jlong ptr,
- jfloat acceleration) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetPointerAcceleration(JNIEnv* env, jobject nativeImplObj, jfloat acceleration) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->setPointerAcceleration(acceleration);
}
-static void nativeSetShowTouches(JNIEnv* /* env */,
- jclass /* clazz */, jlong ptr, jboolean enabled) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetShowTouches(JNIEnv* env, jobject nativeImplObj, jboolean enabled) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->setShowTouches(enabled);
}
-static void nativeSetInteractive(JNIEnv* env,
- jclass clazz, jlong ptr, jboolean interactive) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetInteractive(JNIEnv* env, jobject nativeImplObj, jboolean interactive) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->setInteractive(interactive);
}
-static void nativeReloadCalibration(JNIEnv* env, jclass clazz, jlong ptr) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeReloadCalibration(JNIEnv* env, jobject nativeImplObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->reloadCalibration();
}
-static void nativeVibrate(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
- jlongArray patternObj, jintArray amplitudesObj, jint repeat, jint token) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeVibrate(JNIEnv* env, jobject nativeImplObj, jint deviceId, jlongArray patternObj,
+ jintArray amplitudesObj, jint repeat, jint token) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
size_t patternSize = env->GetArrayLength(patternObj);
if (patternSize > MAX_VIBRATE_PATTERN_SIZE) {
@@ -1940,10 +1923,10 @@ static void nativeVibrate(JNIEnv* env, jclass /* clazz */, jlong ptr, jint devic
im->getInputManager()->getReader().vibrate(deviceId, sequence, repeat, token);
}
-static void nativeVibrateCombined(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
+static void nativeVibrateCombined(JNIEnv* env, jobject nativeImplObj, jint deviceId,
jlongArray patternObj, jobject amplitudesObj, jint repeat,
jint token) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
size_t patternSize = env->GetArrayLength(patternObj);
@@ -1990,21 +1973,20 @@ static void nativeVibrateCombined(JNIEnv* env, jclass /* clazz */, jlong ptr, ji
im->getInputManager()->getReader().vibrate(deviceId, sequence, repeat, token);
}
-static void nativeCancelVibrate(JNIEnv* /* env */,
- jclass /* clazz */, jlong ptr, jint deviceId, jint token) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeCancelVibrate(JNIEnv* env, jobject nativeImplObj, jint deviceId, jint token) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getReader().cancelVibrate(deviceId, token);
}
-static bool nativeIsVibrating(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jint deviceId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static bool nativeIsVibrating(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
return im->getInputManager()->getReader().isVibrating(deviceId);
}
-static jintArray nativeGetVibratorIds(JNIEnv* env, jclass clazz, jlong ptr, jint deviceId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jintArray nativeGetVibratorIds(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
std::vector<int32_t> vibrators = im->getInputManager()->getReader().getVibratorIds(deviceId);
jintArray vibIdArray = env->NewIntArray(vibrators.size());
@@ -2014,8 +1996,8 @@ static jintArray nativeGetVibratorIds(JNIEnv* env, jclass clazz, jlong ptr, jint
return vibIdArray;
}
-static jobject nativeGetLights(JNIEnv* env, jclass clazz, jlong ptr, jint deviceId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jobject nativeGetLights(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
jobject jLights = env->NewObject(gArrayListClassInfo.clazz, gArrayListClassInfo.constructor);
std::vector<InputDeviceLightInfo> lights =
@@ -2059,9 +2041,9 @@ static jobject nativeGetLights(JNIEnv* env, jclass clazz, jlong ptr, jint device
return jLights;
}
-static jint nativeGetLightPlayerId(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
+static jint nativeGetLightPlayerId(JNIEnv* env, jobject nativeImplObj, jint deviceId,
jint lightId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
std::optional<int32_t> ret =
im->getInputManager()->getReader().getLightPlayerId(deviceId, lightId);
@@ -2069,54 +2051,51 @@ static jint nativeGetLightPlayerId(JNIEnv* env, jclass /* clazz */, jlong ptr, j
return static_cast<jint>(ret.value_or(0));
}
-static jint nativeGetLightColor(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
- jint lightId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jint nativeGetLightColor(JNIEnv* env, jobject nativeImplObj, jint deviceId, jint lightId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
std::optional<int32_t> ret =
im->getInputManager()->getReader().getLightColor(deviceId, lightId);
return static_cast<jint>(ret.value_or(0));
}
-static void nativeSetLightPlayerId(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
- jint lightId, jint playerId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetLightPlayerId(JNIEnv* env, jobject nativeImplObj, jint deviceId, jint lightId,
+ jint playerId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getReader().setLightPlayerId(deviceId, lightId, playerId);
}
-static void nativeSetLightColor(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
- jint lightId, jint color) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetLightColor(JNIEnv* env, jobject nativeImplObj, jint deviceId, jint lightId,
+ jint color) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getReader().setLightColor(deviceId, lightId, color);
}
-static jint nativeGetBatteryCapacity(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jint nativeGetBatteryCapacity(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
std::optional<int32_t> ret = im->getInputManager()->getReader().getBatteryCapacity(deviceId);
return static_cast<jint>(ret.value_or(android::os::IInputConstants::INVALID_BATTERY_CAPACITY));
}
-static jint nativeGetBatteryStatus(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jint nativeGetBatteryStatus(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
std::optional<int32_t> ret = im->getInputManager()->getReader().getBatteryStatus(deviceId);
return static_cast<jint>(ret.value_or(BATTERY_STATUS_UNKNOWN));
}
-static void nativeReloadKeyboardLayouts(JNIEnv* /* env */,
- jclass /* clazz */, jlong ptr) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeReloadKeyboardLayouts(JNIEnv* env, jobject nativeImplObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getReader().requestRefreshConfiguration(
InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUTS);
}
-static void nativeReloadDeviceAliases(JNIEnv* /* env */,
- jclass /* clazz */, jlong ptr) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeReloadDeviceAliases(JNIEnv* env, jobject nativeImplObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getReader().requestRefreshConfiguration(
InputReaderConfiguration::CHANGE_DEVICE_ALIAS);
@@ -2131,58 +2110,54 @@ static std::string dumpInputProperties() {
return out;
}
-static jstring nativeDump(JNIEnv* env, jclass /* clazz */, jlong ptr) {
+static jstring nativeDump(JNIEnv* env, jobject nativeImplObj) {
std::string dump = dumpInputProperties();
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->dump(dump);
return env->NewStringUTF(dump.c_str());
}
-static void nativeMonitor(JNIEnv* /* env */, jclass /* clazz */, jlong ptr) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeMonitor(JNIEnv* env, jobject nativeImplObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getReader().monitor();
im->getInputManager()->getDispatcher().monitor();
}
-static jboolean nativeIsInputDeviceEnabled(JNIEnv* env /* env */,
- jclass /* clazz */, jlong ptr, jint deviceId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jboolean nativeIsInputDeviceEnabled(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
return im->getInputManager()->getReader().isInputDeviceEnabled(deviceId);
}
-static void nativeEnableInputDevice(JNIEnv* /* env */,
- jclass /* clazz */, jlong ptr, jint deviceId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeEnableInputDevice(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->setInputDeviceEnabled(deviceId, true);
}
-static void nativeDisableInputDevice(JNIEnv* /* env */,
- jclass /* clazz */, jlong ptr, jint deviceId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeDisableInputDevice(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->setInputDeviceEnabled(deviceId, false);
}
-static void nativeSetPointerIconType(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jint iconId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetPointerIconType(JNIEnv* env, jobject nativeImplObj, jint iconId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->setPointerIconType(iconId);
}
-static void nativeReloadPointerIcons(JNIEnv* /* env */, jclass /* clazz */, jlong ptr) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeReloadPointerIcons(JNIEnv* env, jobject nativeImplObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->reloadPointerIcons();
}
-static void nativeSetCustomPointerIcon(JNIEnv* env, jclass /* clazz */,
- jlong ptr, jobject iconObj) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetCustomPointerIcon(JNIEnv* env, jobject nativeImplObj, jobject iconObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
PointerIcon pointerIcon;
status_t result = android_view_PointerIcon_getLoadedIcon(env, iconObj, &pointerIcon);
@@ -2196,40 +2171,38 @@ static void nativeSetCustomPointerIcon(JNIEnv* env, jclass /* clazz */,
im->setCustomPointerIcon(spriteIcon);
}
-static jboolean nativeCanDispatchToDisplay(JNIEnv* env, jclass /* clazz */, jlong ptr,
- jint deviceId, jint displayId) {
-
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jboolean nativeCanDispatchToDisplay(JNIEnv* env, jobject nativeImplObj, jint deviceId,
+ jint displayId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
return im->getInputManager()->getReader().canDispatchToDisplay(deviceId, displayId);
}
-static void nativeNotifyPortAssociationsChanged(JNIEnv* env, jclass /* clazz */, jlong ptr) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeNotifyPortAssociationsChanged(JNIEnv* env, jobject nativeImplObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getReader().requestRefreshConfiguration(
InputReaderConfiguration::CHANGE_DISPLAY_INFO);
}
-static void nativeNotifyPointerDisplayIdChanged(JNIEnv* env, jclass /* clazz */, jlong ptr) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeNotifyPointerDisplayIdChanged(JNIEnv* env, jobject nativeImplObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->notifyPointerDisplayIdChanged();
}
-static void nativeSetDisplayEligibilityForPointerCapture(JNIEnv* env, jclass /* clazz */, jlong ptr,
+static void nativeSetDisplayEligibilityForPointerCapture(JNIEnv* env, jobject nativeImplObj,
jint displayId, jboolean isEligible) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getDispatcher().setDisplayEligibilityForPointerCapture(displayId,
isEligible);
}
-static void nativeChangeUniqueIdAssociation(JNIEnv* env, jclass /* clazz */, jlong ptr) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeChangeUniqueIdAssociation(JNIEnv* env, jobject nativeImplObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getReader().requestRefreshConfiguration(
InputReaderConfiguration::CHANGE_DISPLAY_INFO);
}
-static void nativeSetMotionClassifierEnabled(JNIEnv* /* env */, jclass /* clazz */, jlong ptr,
- jboolean enabled) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeSetMotionClassifierEnabled(JNIEnv* env, jobject nativeImplObj, jboolean enabled) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->setMotionClassifierEnabled(enabled);
}
@@ -2265,8 +2238,8 @@ static jobject createInputSensorInfo(JNIEnv* env, jstring name, jstring vendor,
return sensorInfo;
}
-static jobjectArray nativeGetSensorList(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static jobjectArray nativeGetSensorList(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
std::vector<InputDeviceSensorInfo> sensors =
im->getInputManager()->getReader().getSensors(deviceId);
@@ -2295,10 +2268,10 @@ static jobjectArray nativeGetSensorList(JNIEnv* env, jclass /* clazz */, jlong p
return arr;
}
-static jboolean nativeEnableSensor(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
+static jboolean nativeEnableSensor(JNIEnv* env, jobject nativeImplObj, jint deviceId,
jint sensorType, jint samplingPeriodUs,
jint maxBatchReportLatencyUs) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
return im->getInputManager()
->getReader()
@@ -2307,18 +2280,18 @@ static jboolean nativeEnableSensor(JNIEnv* env, jclass /* clazz */, jlong ptr, j
std::chrono::microseconds(maxBatchReportLatencyUs));
}
-static void nativeDisableSensor(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
+static void nativeDisableSensor(JNIEnv* env, jobject nativeImplObj, jint deviceId,
jint sensorType) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getReader().disableSensor(deviceId,
static_cast<InputDeviceSensorType>(
sensorType));
}
-static jboolean nativeFlushSensor(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId,
+static jboolean nativeFlushSensor(JNIEnv* env, jobject nativeImplObj, jint deviceId,
jint sensorType) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getReader().flushSensor(deviceId,
static_cast<InputDeviceSensorType>(sensorType));
@@ -2327,8 +2300,8 @@ static jboolean nativeFlushSensor(JNIEnv* env, jclass /* clazz */, jlong ptr, ji
sensorType));
}
-static void nativeCancelCurrentTouch(JNIEnv* env, jclass /* clazz */, jlong ptr) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+static void nativeCancelCurrentTouch(JNIEnv* env, jobject nativeImplObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
im->getInputManager()->getDispatcher().cancelCurrentTouch();
}
@@ -2336,87 +2309,84 @@ static void nativeCancelCurrentTouch(JNIEnv* env, jclass /* clazz */, jlong ptr)
static const JNINativeMethod gInputManagerMethods[] = {
/* name, signature, funcPtr */
- {"nativeInit",
+ {"init",
"(Lcom/android/server/input/InputManagerService;Landroid/content/Context;Landroid/os/"
"MessageQueue;)J",
(void*)nativeInit},
- {"nativeStart", "(J)V", (void*)nativeStart},
- {"nativeSetDisplayViewports", "(J[Landroid/hardware/display/DisplayViewport;)V",
+ {"start", "()V", (void*)nativeStart},
+ {"setDisplayViewports", "([Landroid/hardware/display/DisplayViewport;)V",
(void*)nativeSetDisplayViewports},
- {"nativeGetScanCodeState", "(JIII)I", (void*)nativeGetScanCodeState},
- {"nativeGetKeyCodeState", "(JIII)I", (void*)nativeGetKeyCodeState},
- {"nativeGetSwitchState", "(JIII)I", (void*)nativeGetSwitchState},
- {"nativeHasKeys", "(JII[I[Z)Z", (void*)nativeHasKeys},
- {"nativeGetKeyCodeForKeyLocation", "(JII)I", (void*)nativeGetKeyCodeForKeyLocation},
- {"nativeCreateInputChannel", "(JLjava/lang/String;)Landroid/view/InputChannel;",
+ {"getScanCodeState", "(III)I", (void*)nativeGetScanCodeState},
+ {"getKeyCodeState", "(III)I", (void*)nativeGetKeyCodeState},
+ {"getSwitchState", "(III)I", (void*)nativeGetSwitchState},
+ {"hasKeys", "(II[I[Z)Z", (void*)nativeHasKeys},
+ {"getKeyCodeForKeyLocation", "(II)I", (void*)nativeGetKeyCodeForKeyLocation},
+ {"createInputChannel", "(Ljava/lang/String;)Landroid/view/InputChannel;",
(void*)nativeCreateInputChannel},
- {"nativeCreateInputMonitor", "(JILjava/lang/String;I)Landroid/view/InputChannel;",
+ {"createInputMonitor", "(ILjava/lang/String;I)Landroid/view/InputChannel;",
(void*)nativeCreateInputMonitor},
- {"nativeRemoveInputChannel", "(JLandroid/os/IBinder;)V", (void*)nativeRemoveInputChannel},
- {"nativePilferPointers", "(JLandroid/os/IBinder;)V", (void*)nativePilferPointers},
- {"nativeSetInputFilterEnabled", "(JZ)V", (void*)nativeSetInputFilterEnabled},
- {"nativeSetInTouchMode", "(JZIIZ)Z", (void*)nativeSetInTouchMode},
- {"nativeSetMaximumObscuringOpacityForTouch", "(JF)V",
+ {"removeInputChannel", "(Landroid/os/IBinder;)V", (void*)nativeRemoveInputChannel},
+ {"pilferPointers", "(Landroid/os/IBinder;)V", (void*)nativePilferPointers},
+ {"setInputFilterEnabled", "(Z)V", (void*)nativeSetInputFilterEnabled},
+ {"setInTouchMode", "(ZIIZ)Z", (void*)nativeSetInTouchMode},
+ {"setMaximumObscuringOpacityForTouch", "(F)V",
(void*)nativeSetMaximumObscuringOpacityForTouch},
- {"nativeSetBlockUntrustedTouchesMode", "(JI)V", (void*)nativeSetBlockUntrustedTouchesMode},
- {"nativeInjectInputEvent", "(JLandroid/view/InputEvent;IIIII)I",
- (void*)nativeInjectInputEvent},
- {"nativeVerifyInputEvent", "(JLandroid/view/InputEvent;)Landroid/view/VerifiedInputEvent;",
+ {"setBlockUntrustedTouchesMode", "(I)V", (void*)nativeSetBlockUntrustedTouchesMode},
+ {"injectInputEvent", "(Landroid/view/InputEvent;ZIIII)I", (void*)nativeInjectInputEvent},
+ {"verifyInputEvent", "(Landroid/view/InputEvent;)Landroid/view/VerifiedInputEvent;",
(void*)nativeVerifyInputEvent},
- {"nativeToggleCapsLock", "(JI)V", (void*)nativeToggleCapsLock},
- {"nativeDisplayRemoved", "(JI)V", (void*)nativeDisplayRemoved},
- {"nativeSetFocusedApplication", "(JILandroid/view/InputApplicationHandle;)V",
+ {"toggleCapsLock", "(I)V", (void*)nativeToggleCapsLock},
+ {"displayRemoved", "(I)V", (void*)nativeDisplayRemoved},
+ {"setFocusedApplication", "(ILandroid/view/InputApplicationHandle;)V",
(void*)nativeSetFocusedApplication},
- {"nativeSetFocusedDisplay", "(JI)V", (void*)nativeSetFocusedDisplay},
- {"nativeRequestPointerCapture", "(JLandroid/os/IBinder;Z)V",
- (void*)nativeRequestPointerCapture},
- {"nativeSetInputDispatchMode", "(JZZ)V", (void*)nativeSetInputDispatchMode},
- {"nativeSetSystemUiLightsOut", "(JZ)V", (void*)nativeSetSystemUiLightsOut},
- {"nativeTransferTouchFocus", "(JLandroid/os/IBinder;Landroid/os/IBinder;Z)Z",
+ {"setFocusedDisplay", "(I)V", (void*)nativeSetFocusedDisplay},
+ {"requestPointerCapture", "(Landroid/os/IBinder;Z)V", (void*)nativeRequestPointerCapture},
+ {"setInputDispatchMode", "(ZZ)V", (void*)nativeSetInputDispatchMode},
+ {"setSystemUiLightsOut", "(Z)V", (void*)nativeSetSystemUiLightsOut},
+ {"transferTouchFocus", "(Landroid/os/IBinder;Landroid/os/IBinder;Z)Z",
(void*)nativeTransferTouchFocus},
- {"nativeTransferTouch", "(JLandroid/os/IBinder;)Z", (void*)nativeTransferTouch},
- {"nativeSetPointerSpeed", "(JI)V", (void*)nativeSetPointerSpeed},
- {"nativeSetPointerAcceleration", "(JF)V", (void*)nativeSetPointerAcceleration},
- {"nativeSetShowTouches", "(JZ)V", (void*)nativeSetShowTouches},
- {"nativeSetInteractive", "(JZ)V", (void*)nativeSetInteractive},
- {"nativeReloadCalibration", "(J)V", (void*)nativeReloadCalibration},
- {"nativeVibrate", "(JI[J[III)V", (void*)nativeVibrate},
- {"nativeVibrateCombined", "(JI[JLandroid/util/SparseArray;II)V",
- (void*)nativeVibrateCombined},
- {"nativeCancelVibrate", "(JII)V", (void*)nativeCancelVibrate},
- {"nativeIsVibrating", "(JI)Z", (void*)nativeIsVibrating},
- {"nativeGetVibratorIds", "(JI)[I", (void*)nativeGetVibratorIds},
- {"nativeGetLights", "(JI)Ljava/util/List;", (void*)nativeGetLights},
- {"nativeGetLightPlayerId", "(JII)I", (void*)nativeGetLightPlayerId},
- {"nativeGetLightColor", "(JII)I", (void*)nativeGetLightColor},
- {"nativeSetLightPlayerId", "(JIII)V", (void*)nativeSetLightPlayerId},
- {"nativeSetLightColor", "(JIII)V", (void*)nativeSetLightColor},
- {"nativeGetBatteryCapacity", "(JI)I", (void*)nativeGetBatteryCapacity},
- {"nativeGetBatteryStatus", "(JI)I", (void*)nativeGetBatteryStatus},
- {"nativeReloadKeyboardLayouts", "(J)V", (void*)nativeReloadKeyboardLayouts},
- {"nativeReloadDeviceAliases", "(J)V", (void*)nativeReloadDeviceAliases},
- {"nativeDump", "(J)Ljava/lang/String;", (void*)nativeDump},
- {"nativeMonitor", "(J)V", (void*)nativeMonitor},
- {"nativeIsInputDeviceEnabled", "(JI)Z", (void*)nativeIsInputDeviceEnabled},
- {"nativeEnableInputDevice", "(JI)V", (void*)nativeEnableInputDevice},
- {"nativeDisableInputDevice", "(JI)V", (void*)nativeDisableInputDevice},
- {"nativeSetPointerIconType", "(JI)V", (void*)nativeSetPointerIconType},
- {"nativeReloadPointerIcons", "(J)V", (void*)nativeReloadPointerIcons},
- {"nativeSetCustomPointerIcon", "(JLandroid/view/PointerIcon;)V",
+ {"transferTouch", "(Landroid/os/IBinder;)Z", (void*)nativeTransferTouch},
+ {"setPointerSpeed", "(I)V", (void*)nativeSetPointerSpeed},
+ {"setPointerAcceleration", "(F)V", (void*)nativeSetPointerAcceleration},
+ {"setShowTouches", "(Z)V", (void*)nativeSetShowTouches},
+ {"setInteractive", "(Z)V", (void*)nativeSetInteractive},
+ {"reloadCalibration", "()V", (void*)nativeReloadCalibration},
+ {"vibrate", "(I[J[III)V", (void*)nativeVibrate},
+ {"vibrateCombined", "(I[JLandroid/util/SparseArray;II)V", (void*)nativeVibrateCombined},
+ {"cancelVibrate", "(II)V", (void*)nativeCancelVibrate},
+ {"isVibrating", "(I)Z", (void*)nativeIsVibrating},
+ {"getVibratorIds", "(I)[I", (void*)nativeGetVibratorIds},
+ {"getLights", "(I)Ljava/util/List;", (void*)nativeGetLights},
+ {"getLightPlayerId", "(II)I", (void*)nativeGetLightPlayerId},
+ {"getLightColor", "(II)I", (void*)nativeGetLightColor},
+ {"setLightPlayerId", "(III)V", (void*)nativeSetLightPlayerId},
+ {"setLightColor", "(III)V", (void*)nativeSetLightColor},
+ {"getBatteryCapacity", "(I)I", (void*)nativeGetBatteryCapacity},
+ {"getBatteryStatus", "(I)I", (void*)nativeGetBatteryStatus},
+ {"reloadKeyboardLayouts", "()V", (void*)nativeReloadKeyboardLayouts},
+ {"reloadDeviceAliases", "()V", (void*)nativeReloadDeviceAliases},
+ {"dump", "()Ljava/lang/String;", (void*)nativeDump},
+ {"monitor", "()V", (void*)nativeMonitor},
+ {"isInputDeviceEnabled", "(I)Z", (void*)nativeIsInputDeviceEnabled},
+ {"enableInputDevice", "(I)V", (void*)nativeEnableInputDevice},
+ {"disableInputDevice", "(I)V", (void*)nativeDisableInputDevice},
+ {"setPointerIconType", "(I)V", (void*)nativeSetPointerIconType},
+ {"reloadPointerIcons", "()V", (void*)nativeReloadPointerIcons},
+ {"setCustomPointerIcon", "(Landroid/view/PointerIcon;)V",
(void*)nativeSetCustomPointerIcon},
- {"nativeCanDispatchToDisplay", "(JII)Z", (void*)nativeCanDispatchToDisplay},
- {"nativeNotifyPortAssociationsChanged", "(J)V", (void*)nativeNotifyPortAssociationsChanged},
- {"nativeChangeUniqueIdAssociation", "(J)V", (void*)nativeChangeUniqueIdAssociation},
- {"nativeNotifyPointerDisplayIdChanged", "(J)V", (void*)nativeNotifyPointerDisplayIdChanged},
- {"nativeSetDisplayEligibilityForPointerCapture", "(JIZ)V",
+ {"canDispatchToDisplay", "(II)Z", (void*)nativeCanDispatchToDisplay},
+ {"notifyPortAssociationsChanged", "()V", (void*)nativeNotifyPortAssociationsChanged},
+ {"changeUniqueIdAssociation", "()V", (void*)nativeChangeUniqueIdAssociation},
+ {"notifyPointerDisplayIdChanged", "()V", (void*)nativeNotifyPointerDisplayIdChanged},
+ {"setDisplayEligibilityForPointerCapture", "(IZ)V",
(void*)nativeSetDisplayEligibilityForPointerCapture},
- {"nativeSetMotionClassifierEnabled", "(JZ)V", (void*)nativeSetMotionClassifierEnabled},
- {"nativeGetSensorList", "(JI)[Landroid/hardware/input/InputSensorInfo;",
+ {"setMotionClassifierEnabled", "(Z)V", (void*)nativeSetMotionClassifierEnabled},
+ {"getSensorList", "(I)[Landroid/hardware/input/InputSensorInfo;",
(void*)nativeGetSensorList},
- {"nativeEnableSensor", "(JIIII)Z", (void*)nativeEnableSensor},
- {"nativeDisableSensor", "(JII)V", (void*)nativeDisableSensor},
- {"nativeFlushSensor", "(JII)Z", (void*)nativeFlushSensor},
- {"nativeCancelCurrentTouch", "(J)V", (void*)nativeCancelCurrentTouch},
+ {"enableSensor", "(IIII)Z", (void*)nativeEnableSensor},
+ {"disableSensor", "(II)V", (void*)nativeDisableSensor},
+ {"flushSensor", "(II)Z", (void*)nativeFlushSensor},
+ {"cancelCurrentTouch", "()V", (void*)nativeCancelCurrentTouch},
};
#define FIND_CLASS(var, className) \
@@ -2436,11 +2406,21 @@ static const JNINativeMethod gInputManagerMethods[] = {
LOG_FATAL_IF(! (var), "Unable to find field " fieldName);
int register_android_server_InputManager(JNIEnv* env) {
- int res = jniRegisterNativeMethods(env, "com/android/server/input/InputManagerService",
- gInputManagerMethods, NELEM(gInputManagerMethods));
- (void) res; // Faked use when LOG_NDEBUG.
+ int res = jniRegisterNativeMethods(env,
+ "com/android/server/input/"
+ "NativeInputManagerService$NativeImpl",
+ gInputManagerMethods, NELEM(gInputManagerMethods));
+ (void)res; // Faked use when LOG_NDEBUG.
LOG_FATAL_IF(res < 0, "Unable to register native methods.");
+ FIND_CLASS(gNativeInputManagerServiceImpl.clazz,
+ "com/android/server/input/"
+ "NativeInputManagerService$NativeImpl");
+ gNativeInputManagerServiceImpl.clazz =
+ jclass(env->NewGlobalRef(gNativeInputManagerServiceImpl.clazz));
+ gNativeInputManagerServiceImpl.mPtr =
+ env->GetFieldID(gNativeInputManagerServiceImpl.clazz, "mPtr", "J");
+
// Callbacks
jclass clazz;
@@ -2499,9 +2479,6 @@ int register_android_server_InputManager(JNIEnv* env) {
"dispatchUnhandledKey",
"(Landroid/os/IBinder;Landroid/view/KeyEvent;I)Landroid/view/KeyEvent;");
- GET_METHOD_ID(gServiceClassInfo.checkInjectEventsPermission, clazz,
- "checkInjectEventsPermission", "(II)Z");
-
GET_METHOD_ID(gServiceClassInfo.onPointerDownOutsideFocus, clazz,
"onPointerDownOutsideFocus", "(Landroid/os/IBinder;)V");
diff --git a/services/devicepolicy/TEST_MAPPING b/services/devicepolicy/TEST_MAPPING
index 3d86cf30f38e..72bba11c5366 100644
--- a/services/devicepolicy/TEST_MAPPING
+++ b/services/devicepolicy/TEST_MAPPING
@@ -16,5 +16,15 @@
{
"name": "CtsDevicePolicyManagerTestCases"
}
+ ],
+ "presubmit": [
+ {
+ "name": "CtsDevicePolicyManagerTestCases",
+ "options": [
+ {
+ "include-filter": "com.android.cts.devicepolicy.ManagedProfileTest#testParentProfileApiDisabled"
+ }
+ ]
+ }
]
}
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index e1fe1d8433ef..90fd8edacce3 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -685,11 +685,13 @@ public class MidiService extends IMidiManager.Stub {
private boolean hasNonMidiUuids(BluetoothDevice btDevice) {
ParcelUuid[] uuidParcels = btDevice.getUuids();
- // The assumption is that these services are indicative of devices that
- // ARE NOT MIDI devices.
- for (ParcelUuid parcel : uuidParcels) {
- if (mNonMidiUUIDs.contains(parcel)) {
- return true;
+ if (uuidParcels != null) {
+ // The assumption is that these services are indicative of devices that
+ // ARE NOT MIDI devices.
+ for (ParcelUuid parcel : uuidParcels) {
+ if (mNonMidiUUIDs.contains(parcel)) {
+ return true;
+ }
}
}
return false;
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index e6fd9164d96b..d305fc5d7dc4 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -618,10 +618,17 @@ public class DataManager {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(TelecomManager.ACTION_DEFAULT_DIALER_CHANGED);
intentFilter.addAction(SmsApplication.ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL);
- BroadcastReceiver broadcastReceiver = new PerUserBroadcastReceiver(userId);
- mBroadcastReceivers.put(userId, broadcastReceiver);
- mContext.registerReceiverAsUser(
- broadcastReceiver, UserHandle.of(userId), intentFilter, null, null);
+
+ if (mBroadcastReceivers.get(userId) == null) {
+ BroadcastReceiver broadcastReceiver = new PerUserBroadcastReceiver(userId);
+ mBroadcastReceivers.put(userId, broadcastReceiver);
+ mContext.registerReceiverAsUser(
+ broadcastReceiver, UserHandle.of(userId), intentFilter, null, null);
+ } else {
+ // Stopped was not called on this user before setup is called again. This
+ // could happen during consecutive rapid user switching.
+ if (DEBUG) Log.d(TAG, "PerUserBroadcastReceiver was registered for: " + userId);
+ }
ContentObserver contactsContentObserver = new ContactsContentObserver(
BackgroundThread.getHandler());
@@ -639,9 +646,15 @@ public class DataManager {
// Should never occur for local calls.
}
- PackageMonitor packageMonitor = new PerUserPackageMonitor();
- packageMonitor.register(mContext, null, UserHandle.of(userId), true);
- mPackageMonitors.put(userId, packageMonitor);
+ if (mPackageMonitors.get(userId) == null) {
+ PackageMonitor packageMonitor = new PerUserPackageMonitor();
+ packageMonitor.register(mContext, null, UserHandle.of(userId), true);
+ mPackageMonitors.put(userId, packageMonitor);
+ } else {
+ // Stopped was not called on this user before setup is called again. This
+ // could happen during consecutive rapid user switching.
+ if (DEBUG) Log.d(TAG, "PerUserPackageMonitor was registered for: " + userId);
+ }
if (userId == UserHandle.USER_SYSTEM) {
// The call log and MMS/SMS messages are shared across user profiles. So only need
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
index 77cf54363c62..c0b4f0fe5812 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
@@ -671,7 +671,7 @@ public final class BackgroundRestrictionTest {
mAppFGSTracker.onForegroundServiceStateChanged(testPkgName, testUid,
testPid, true);
mAppFGSTracker.onForegroundServiceNotificationUpdated(
- testPkgName, testUid, notificationId);
+ testPkgName, testUid, notificationId, false);
mAppFGSTracker.mNotificationListener.onNotificationPosted(new StatusBarNotification(
testPkgName, null, notificationId, null, testUid, testPid,
new Notification(), UserHandle.of(testUser), null, mCurrentTimeMillis), null);
@@ -848,7 +848,7 @@ public final class BackgroundRestrictionTest {
// Pretend we have the notification dismissed.
mAppFGSTracker.onForegroundServiceNotificationUpdated(
- testPkgName, testUid, -notificationId);
+ testPkgName, testUid, notificationId, true);
clearInvocations(mInjector.getAppStandbyInternal());
clearInvocations(mInjector.getNotificationManager());
clearInvocations(mBgRestrictionController);
@@ -885,7 +885,7 @@ public final class BackgroundRestrictionTest {
// Pretend notification is back on.
mAppFGSTracker.onForegroundServiceNotificationUpdated(
- testPkgName, testUid, notificationId);
+ testPkgName, testUid, notificationId, false);
// Now we'll prompt the user even it has a FGS with notification.
bgPromptFgsWithNotiToBgRestricted.set(true);
clearInvocations(mInjector.getAppStandbyInternal());
@@ -1224,7 +1224,7 @@ public final class BackgroundRestrictionTest {
mAppFGSTracker.onForegroundServiceStateChanged(testPkgName1, testUid1,
testPid1, true);
mAppFGSTracker.onForegroundServiceNotificationUpdated(
- testPkgName1, testUid1, fgsNotificationId);
+ testPkgName1, testUid1, fgsNotificationId, false);
mAppFGSTracker.mNotificationListener.onNotificationPosted(new StatusBarNotification(
testPkgName1, null, fgsNotificationId, null, testUid1, testPid1,
new Notification(), UserHandle.of(testUser1), null, mCurrentTimeMillis), null);
@@ -1235,7 +1235,7 @@ public final class BackgroundRestrictionTest {
// Pretend we have the notification dismissed.
mAppFGSTracker.onForegroundServiceNotificationUpdated(
- testPkgName1, testUid1, -fgsNotificationId);
+ testPkgName1, testUid1, fgsNotificationId, true);
// Verify we have the notification.
notificationId = checkNotificationShown(
@@ -1500,7 +1500,7 @@ public final class BackgroundRestrictionTest {
if (withNotification) {
final int notificationId = 1000;
mAppFGSTracker.onForegroundServiceNotificationUpdated(
- packageName, uid, notificationId);
+ packageName, uid, notificationId, false);
final StatusBarNotification noti = new StatusBarNotification(
packageName, null, notificationId, null, uid, pid,
new Notification(), UserHandle.of(UserHandle.getUserId(uid)),
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 9a4f8e261124..43ba39adcb85 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
@@ -2164,6 +2164,49 @@ public class QuotaControllerTest {
}
}
+ @Test
+ public void testIsWithinEJQuotaLocked_TempAllowlisting_Restricted() {
+ setDischarging();
+ JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_TempAllowlisting_Restricted", 1);
+ setStandbyBucket(RESTRICTED_INDEX, js);
+ setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 10 * MINUTE_IN_MILLIS);
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true);
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true);
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5), true);
+ synchronized (mQuotaController.mLock) {
+ assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
+ }
+
+ setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+ final long gracePeriodMs = 15 * SECOND_IN_MILLIS;
+ setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, gracePeriodMs);
+ Handler handler = mQuotaController.getHandler();
+ spyOn(handler);
+
+ // The temp allowlist should not enable RESTRICTED apps' to schedule & start EJs if they're
+ // out of quota.
+ mTempAllowlistListener.onAppAdded(mSourceUid);
+ synchronized (mQuotaController.mLock) {
+ assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mTempAllowlistListener.onAppRemoved(mSourceUid);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ // Still in grace period
+ synchronized (mQuotaController.mLock) {
+ assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
+ }
+ advanceElapsedClock(6 * SECOND_IN_MILLIS);
+ // Out of grace period.
+ synchronized (mQuotaController.mLock) {
+ assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
+ }
+ }
+
/**
* Tests that Timers properly track sessions when an app becomes top and is closed.
*/
@@ -5559,6 +5602,111 @@ public class QuotaControllerTest {
mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
}
+ @Test
+ public void testEJTimerTracking_TempAllowlisting_Restricted() {
+ setDischarging();
+ setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
+ final long gracePeriodMs = 15 * SECOND_IN_MILLIS;
+ setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, gracePeriodMs);
+ Handler handler = mQuotaController.getHandler();
+ spyOn(handler);
+
+ JobStatus job = createExpeditedJobStatus("testEJTimerTracking_TempAllowlisting_Restricted", 1);
+ setStandbyBucket(RESTRICTED_INDEX, job);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job, null);
+ }
+ assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+ List<TimingSession> expected = new ArrayList<>();
+
+ long start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(job);
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(job, job, true);
+ }
+ expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+ assertEquals(expected,
+ mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ advanceElapsedClock(SECOND_IN_MILLIS);
+
+ // Job starts after app is added to temp allowlist and stops before removal.
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mTempAllowlistListener.onAppAdded(mSourceUid);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job, null);
+ mQuotaController.prepareForExecutionLocked(job);
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(job, null, false);
+ }
+ expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+ assertEquals(expected,
+ mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ // Job starts after app is added to temp allowlist and stops after removal,
+ // before grace period ends.
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mTempAllowlistListener.onAppAdded(mSourceUid);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job, null);
+ mQuotaController.prepareForExecutionLocked(job);
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ mTempAllowlistListener.onAppRemoved(mSourceUid);
+ long elapsedGracePeriodMs = 2 * SECOND_IN_MILLIS;
+ advanceElapsedClock(elapsedGracePeriodMs);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(job, null, false);
+ }
+ expected.add(createTimingSession(start, 12 * SECOND_IN_MILLIS, 1));
+ assertEquals(expected,
+ mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ advanceElapsedClock(SECOND_IN_MILLIS);
+ elapsedGracePeriodMs += SECOND_IN_MILLIS;
+
+ // Job starts during grace period and ends after grace period ends
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job, null);
+ mQuotaController.prepareForExecutionLocked(job);
+ }
+ final long remainingGracePeriod = gracePeriodMs - elapsedGracePeriodMs;
+ advanceElapsedClock(remainingGracePeriod);
+ // Wait for handler to update Timer
+ // Can't directly evaluate the message because for some reason, the captured message returns
+ // the wrong 'what' even though the correct message goes to the handler and the correct
+ // path executes.
+ verify(handler, timeout(gracePeriodMs + 5 * SECOND_IN_MILLIS)).handleMessage(any());
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS + remainingGracePeriod, 1));
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(job, job, true);
+ }
+ assertEquals(expected,
+ mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ // Job starts and runs completely after temp allowlist grace period.
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(job, null);
+ mQuotaController.prepareForExecutionLocked(job);
+ }
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(job, job, true);
+ }
+ expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
+ assertEquals(expected,
+ mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+
/**
* Tests that Timers properly track sessions when TOP state and temp allowlisting overlaps.
*/
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeSettingsHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeSettingsHelper.java
index cd70020f5c28..b76abe6c74aa 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeSettingsHelper.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeSettingsHelper.java
@@ -84,6 +84,8 @@ public class FakeSettingsHelper extends SettingsHelper {
private final Setting mBackgroundThrottlePackageWhitelistSetting = new Setting(
Collections.emptySet());
private final Setting mGnssMeasurementsFullTrackingSetting = new Setting(Boolean.FALSE);
+ private final Setting mAdasPackageAllowlist = new Setting(
+ new PackageTagsList.Builder().build());
private final Setting mIgnoreSettingsAllowlist = new Setting(
new PackageTagsList.Builder().build());
private final Setting mBackgroundThrottleProximityAlertIntervalSetting = new Setting(
@@ -194,10 +196,29 @@ public class FakeSettingsHelper extends SettingsHelper {
}
@Override
+ public PackageTagsList getAdasAllowlist() {
+ return mAdasPackageAllowlist.getValue(PackageTagsList.class);
+ }
+
+ @Override
+ public void addAdasAllowlistChangedListener(GlobalSettingChangedListener listener) {
+ mAdasPackageAllowlist.addListener(listener);
+ }
+
+ @Override
+ public void removeAdasAllowlistChangedListener(GlobalSettingChangedListener listener) {
+ mAdasPackageAllowlist.removeListener(listener);
+ }
+
+ @Override
public PackageTagsList getIgnoreSettingsAllowlist() {
return mIgnoreSettingsAllowlist.getValue(PackageTagsList.class);
}
+ public void setAdasSettingsAllowlist(PackageTagsList newValue) {
+ mAdasPackageAllowlist.setValue(newValue);
+ }
+
public void setIgnoreSettingsAllowlist(PackageTagsList newValue) {
mIgnoreSettingsAllowlist.setValue(newValue);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index d8f409dfce66..71cc65b484ee 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -1107,6 +1107,10 @@ public class LocationProviderManagerTest {
doReturn(true).when(mPackageManager).hasSystemFeature(FEATURE_AUTOMOTIVE);
doReturn(true).when(mResources).getBoolean(R.bool.config_defaultAdasGnssLocationEnabled);
+ mInjector.getSettingsHelper().setAdasSettingsAllowlist(
+ new PackageTagsList.Builder().add(
+ IDENTITY.getPackageName()).build());
+
createManager(GPS_PROVIDER);
ILocationListener listener1 = createMockLocationListener();
@@ -1136,6 +1140,10 @@ public class LocationProviderManagerTest {
doReturn(true).when(mPackageManager).hasSystemFeature(FEATURE_AUTOMOTIVE);
doReturn(true).when(mResources).getBoolean(R.bool.config_defaultAdasGnssLocationEnabled);
+ mInjector.getSettingsHelper().setAdasSettingsAllowlist(
+ new PackageTagsList.Builder().add(
+ IDENTITY.getPackageName()).build());
+
createManager(GPS_PROVIDER);
ILocationListener listener1 = createMockLocationListener();
@@ -1160,11 +1168,16 @@ public class LocationProviderManagerTest {
@Test
public void testProviderRequest_AdasGnssBypass_ProviderDisabled_AdasDisabled() {
+ doReturn(true).when(mPackageManager).hasSystemFeature(FEATURE_AUTOMOTIVE);
+ doReturn(true).when(mResources).getBoolean(R.bool.config_defaultAdasGnssLocationEnabled);
+
mInjector.getSettingsHelper().setIgnoreSettingsAllowlist(
new PackageTagsList.Builder().add(
IDENTITY.getPackageName()).build());
- doReturn(true).when(mPackageManager).hasSystemFeature(FEATURE_AUTOMOTIVE);
- doReturn(true).when(mResources).getBoolean(R.bool.config_defaultAdasGnssLocationEnabled);
+
+ mInjector.getSettingsHelper().setAdasSettingsAllowlist(
+ new PackageTagsList.Builder().add(
+ IDENTITY.getPackageName()).build());
createManager(GPS_PROVIDER);
diff --git a/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java b/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java
new file mode 100644
index 000000000000..00f4c3908f26
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.server.am;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.SystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test class for {@link DropboxRateLimiter}.
+ *
+ * Build/Install/Run:
+ * atest DropboxRateLimiterTest
+ */
+public class DropboxRateLimiterTest {
+ private DropboxRateLimiter mRateLimiter;
+ private TestClock mClock;
+
+ @Before
+ public void setUp() {
+ mClock = new TestClock();
+ mRateLimiter = new DropboxRateLimiter(mClock);
+ }
+
+ @Test
+ public void testMultipleProcesses() {
+ // The first 5 entries should not be rate limited.
+ assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
+ assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
+ assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
+ assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
+ assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
+ // Different processes and tags should not get rate limited either.
+ assertFalse(mRateLimiter.shouldRateLimit("tag", "process2"));
+ assertFalse(mRateLimiter.shouldRateLimit("tag2", "process"));
+ // The 6th entry of the same process should be rate limited.
+ assertTrue(mRateLimiter.shouldRateLimit("tag", "process"));
+ }
+
+ @Test
+ public void testBufferClearing() throws Exception {
+ // The first 5 entries should not be rate limited.
+ assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
+ assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
+ assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
+ assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
+ assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
+ // The 6th entry of the same process should be rate limited.
+ assertTrue(mRateLimiter.shouldRateLimit("tag", "process"));
+
+ // After 11 seconds there should be nothing left in the buffer and the same type of entry
+ // should not get rate limited anymore.
+ mClock.setOffsetMillis(11000);
+
+ assertFalse(mRateLimiter.shouldRateLimit("tag", "process"));
+ }
+
+ private static class TestClock implements DropboxRateLimiter.Clock {
+ long mOffsetMillis = 0L;
+
+ public long uptimeMillis() {
+ return mOffsetMillis + SystemClock.uptimeMillis();
+ }
+
+ public void setOffsetMillis(long millis) {
+ mOffsetMillis = millis;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/backup/restore/FullRestoreEngineTest.java b/services/tests/servicestests/src/com/android/server/backup/restore/FullRestoreEngineTest.java
new file mode 100644
index 000000000000..049c745fc128
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/backup/restore/FullRestoreEngineTest.java
@@ -0,0 +1,150 @@
+/*
+ * 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.server.backup.restore;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.backup.BackupAgent;
+import android.platform.test.annotations.Presubmit;
+import android.system.OsConstants;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.backup.FileMetadata;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class FullRestoreEngineTest {
+ private static final String DEFAULT_PACKAGE_NAME = "package";
+ private static final String DEFAULT_DOMAIN_NAME = "domain";
+ private static final String NEW_PACKAGE_NAME = "new_package";
+ private static final String NEW_DOMAIN_NAME = "new_domain";
+
+ private FullRestoreEngine mRestoreEngine;
+
+ @Before
+ public void setUp() {
+ mRestoreEngine = new FullRestoreEngine();
+ }
+
+ @Test
+ public void shouldSkipReadOnlyDir_skipsAllReadonlyDirsAndTheirChildren() {
+ // Create the file tree.
+ TestFile[] testFiles = new TestFile[] {
+ TestFile.dir("root"),
+ TestFile.file("root/auth_token"),
+ TestFile.dir("root/media"),
+ TestFile.file("root/media/picture1.png"),
+ TestFile.file("root/push_token.txt"),
+ TestFile.dir("root/read-only-dir-1").markReadOnly().expectSkipped(),
+ TestFile.dir("root/read-only-dir-1/writable-subdir").expectSkipped(),
+ TestFile.file("root/read-only-dir-1/writable-subdir/writable-file").expectSkipped(),
+ TestFile.dir("root/read-only-dir-1/writable-subdir/read-only-subdir-2")
+ .markReadOnly().expectSkipped(),
+ TestFile.file("root/read-only-dir-1/writable-file").expectSkipped(),
+ TestFile.file("root/random-stuff.txt"),
+ TestFile.dir("root/database"),
+ TestFile.file("root/database/users.db"),
+ TestFile.dir("root/read-only-dir-2").markReadOnly().expectSkipped(),
+ TestFile.file("root/read-only-dir-2/writable-file-1").expectSkipped(),
+ TestFile.file("root/read-only-dir-2/writable-file-2").expectSkipped(),
+ };
+
+ assertCorrectItemsAreSkipped(testFiles);
+ }
+
+ @Test
+ public void shouldSkipReadOnlyDir_onlySkipsChildrenUnderTheSamePackage() {
+ TestFile[] testFiles = new TestFile[]{
+ TestFile.dir("read-only-dir").markReadOnly().expectSkipped(),
+ TestFile.file("read-only-dir/file").expectSkipped(),
+ TestFile.file("read-only-dir/file-from-different-package")
+ .setPackage(NEW_PACKAGE_NAME),
+ };
+
+ assertCorrectItemsAreSkipped(testFiles);
+ }
+
+ @Test
+ public void shouldSkipReadOnlyDir_onlySkipsChildrenUnderTheSameDomain() {
+ TestFile[] testFiles = new TestFile[]{
+ TestFile.dir("read-only-dir").markReadOnly().expectSkipped(),
+ TestFile.file("read-only-dir/file").expectSkipped(),
+ TestFile.file("read-only-dir/file-from-different-domain")
+ .setDomain(NEW_DOMAIN_NAME),
+ };
+
+ assertCorrectItemsAreSkipped(testFiles);
+ }
+
+ private void assertCorrectItemsAreSkipped(TestFile[] testFiles) {
+ // Verify all directories marked with .expectSkipped are skipped.
+ for (TestFile testFile : testFiles) {
+ boolean actualExcluded = mRestoreEngine.shouldSkipReadOnlyDir(testFile.mMetadata);
+ boolean expectedExcluded = testFile.mShouldSkip;
+ assertWithMessage(testFile.mMetadata.path).that(actualExcluded).isEqualTo(
+ expectedExcluded);
+ }
+ }
+
+ private static class TestFile {
+ private final FileMetadata mMetadata;
+ private boolean mShouldSkip;
+
+ static TestFile dir(String path) {
+ return new TestFile(path, BackupAgent.TYPE_DIRECTORY);
+ }
+
+ static TestFile file(String path) {
+ return new TestFile(path, BackupAgent.TYPE_FILE);
+ }
+
+ TestFile markReadOnly() {
+ mMetadata.mode = 0;
+ return this;
+ }
+
+ TestFile expectSkipped() {
+ mShouldSkip = true;
+ return this;
+ }
+
+ TestFile setPackage(String packageName) {
+ mMetadata.packageName = packageName;
+ return this;
+ }
+
+ TestFile setDomain(String domain) {
+ mMetadata.domain = domain;
+ return this;
+ }
+
+ private TestFile(String path, int type) {
+ FileMetadata metadata = new FileMetadata();
+ metadata.path = path;
+ metadata.type = type;
+ metadata.packageName = DEFAULT_PACKAGE_NAME;
+ metadata.domain = DEFAULT_DOMAIN_NAME;
+ metadata.mode = OsConstants.S_IWUSR; // Mark as writable.
+ mMetadata = metadata;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java b/services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java
index b154d6f6db0c..1171518130cc 100644
--- a/services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java
@@ -110,10 +110,7 @@ public class BackupTransportClientTest {
Thread thread = new Thread(() -> {
try {
- /*String name =*/ client.transportDirName();
- fail("transportDirName should be cancelled");
- } catch (CancellationException ex) {
- // This is expected.
+ assertThat(client.transportDirName()).isNull();
} catch (Exception ex) {
fail("unexpected Exception: " + ex.getClass().getCanonicalName());
}
@@ -189,7 +186,7 @@ public class BackupTransportClientTest {
}
@Test
- public void testFinishBackup_canceledBeforeCompletion_throwsException() throws Exception {
+ public void testFinishBackup_canceledBeforeCompletion_returnsError() throws Exception {
TestCallbacksFakeTransportBinder binder = new TestCallbacksFakeTransportBinder();
BackupTransportClient client = new BackupTransportClient(binder);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
index 18f264277b41..112db7612f29 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
@@ -61,7 +61,8 @@ public class ActiveSourceActionTest {
public void setUp() throws Exception {
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
- mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList()) {
+ mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(),
+ new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
AudioManager getAudioManager() {
return new AudioManager() {
@@ -93,6 +94,7 @@ public class ActiveSourceActionTest {
mHdmiControlService.setCecController(hdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.initService();
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
mHdmiControlService.setPowerManager(mPowerManager);
mPhysicalAddress = 0x2000;
@@ -153,7 +155,6 @@ public class ActiveSourceActionTest {
mHdmiControlService);
audioDevice.init();
mLocalDevices.add(audioDevice);
- mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
index e4c5ad6769d7..e4eecc6f6fa0 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
@@ -68,7 +68,8 @@ public class ArcInitiationActionFromAvrTest {
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
HdmiControlService hdmiControlService =
- new HdmiControlService(mContextSpy, Collections.emptyList()) {
+ new HdmiControlService(mContextSpy, Collections.emptyList(),
+ new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
boolean isPowerStandby() {
return false;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
index d73cdb5f53b0..5b114661fae8 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
@@ -68,7 +68,8 @@ public class ArcTerminationActionFromAvrTest {
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
HdmiControlService hdmiControlService =
- new HdmiControlService(mContextSpy, Collections.emptyList()) {
+ new HdmiControlService(mContextSpy, Collections.emptyList(),
+ new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
AudioManager getAudioManager() {
return mAudioManager;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java
new file mode 100644
index 000000000000..e06877f9144e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java
@@ -0,0 +1,570 @@
+/*
+ * Copyright (C) 2021 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.hdmi;
+
+import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM;
+
+import static com.android.server.hdmi.HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP;
+import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_BOOT_UP;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
+
+import static org.mockito.AdditionalMatchers.not;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.hardware.hdmi.DeviceFeatures;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.HdmiPortInfo;
+import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceVolumeManager;
+import android.media.AudioManager;
+import android.media.VolumeInfo;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.server.SystemService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+
+/**
+ * Tests that Absolute Volume Control (AVC) is enabled and disabled correctly, and that
+ * the device responds correctly to incoming <Report Audio Status> messages and API calls
+ * from AudioService when AVC is active.
+ *
+ * This is an abstract base class. Concrete subclasses specify the type of the local device, and the
+ * type of the System Audio device. This allows each test to be run for multiple setups.
+ *
+ * We test the following pairs of (local device, System Audio device):
+ * (Playback, TV): {@link PlaybackDeviceToTvAvcTest}
+ * (Playback, Audio System): {@link PlaybackDeviceToAudioSystemAvcTest}
+ * (TV, Audio System): {@link TvToAudioSystemAvcTest}
+ */
+public abstract class BaseAbsoluteVolumeControlTest {
+ private HdmiControlService mHdmiControlService;
+ private HdmiCecController mHdmiCecController;
+ private HdmiCecLocalDevice mHdmiCecLocalDevice;
+ private FakeHdmiCecConfig mHdmiCecConfig;
+ private FakePowerManagerWrapper mPowerManager;
+ private Looper mLooper;
+ private Context mContextSpy;
+ private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
+
+ @Mock protected AudioManager mAudioManager;
+ protected FakeAudioDeviceVolumeManagerWrapper mAudioDeviceVolumeManager;
+
+ protected TestLooper mTestLooper = new TestLooper();
+ protected FakeNativeWrapper mNativeWrapper;
+
+ // Audio Status given by the System Audio device in its initial <Report Audio Status> that
+ // triggers AVC being enabled
+ private static final AudioStatus INITIAL_SYSTEM_AUDIO_DEVICE_STATUS =
+ new AudioStatus(50, false);
+
+ // VolumeInfo passed to AudioDeviceVolumeManager#setDeviceAbsoluteVolumeBehavior to enable AVC
+ private static final VolumeInfo ENABLE_AVC_VOLUME_INFO =
+ new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
+ .setMuted(INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getMute())
+ .setVolumeIndex(INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getVolume())
+ .setMaxVolumeIndex(AudioStatus.MAX_VOLUME)
+ .setMinVolumeIndex(AudioStatus.MIN_VOLUME)
+ .build();
+
+ protected abstract HdmiCecLocalDevice createLocalDevice(HdmiControlService hdmiControlService);
+
+ protected abstract int getPhysicalAddress();
+ protected abstract int getDeviceType();
+ protected abstract AudioDeviceAttributes getAudioOutputDevice();
+
+ protected abstract int getSystemAudioDeviceLogicalAddress();
+ protected abstract int getSystemAudioDeviceType();
+
+ @Before
+ public void setUp() throws RemoteException {
+ MockitoAnnotations.initMocks(this);
+
+ mContextSpy = spy(new ContextWrapper(
+ InstrumentationRegistry.getInstrumentation().getTargetContext()));
+
+ mAudioDeviceVolumeManager = spy(new FakeAudioDeviceVolumeManagerWrapper());
+
+ mHdmiControlService =
+ new HdmiControlService(InstrumentationRegistry.getTargetContext(),
+ Collections.singletonList(getDeviceType()),
+ mAudioDeviceVolumeManager) {
+ @Override
+ AudioManager getAudioManager() {
+ return mAudioManager;
+ }
+
+ @Override
+ protected void writeStringSystemProperty(String key, String value) {
+ // do nothing
+ }
+ };
+
+ mLooper = mTestLooper.getLooper();
+ mHdmiControlService.setIoLooper(mLooper);
+
+ mHdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
+ mHdmiCecConfig.setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+ HdmiControlManager.HDMI_CEC_VERSION_2_0);
+ mHdmiCecConfig.setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
+ HdmiControlManager.VOLUME_CONTROL_DISABLED);
+ mHdmiControlService.setHdmiCecConfig(mHdmiCecConfig);
+
+ mNativeWrapper = new FakeNativeWrapper();
+ mNativeWrapper.setPhysicalAddress(getPhysicalAddress());
+ mNativeWrapper.setPollAddressResponse(Constants.ADDR_TV, SendMessageResult.SUCCESS);
+
+ mHdmiCecController = HdmiCecController.createWithNativeWrapper(
+ mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
+ mHdmiControlService.setCecController(mHdmiCecController);
+ mHdmiControlService.setHdmiMhlController(
+ HdmiMhlControllerStub.create(mHdmiControlService));
+ mHdmiControlService.initService();
+ mPowerManager = new FakePowerManagerWrapper(mContextSpy);
+ mHdmiControlService.setPowerManager(mPowerManager);
+
+ mHdmiCecLocalDevice = createLocalDevice(mHdmiControlService);
+ mHdmiCecLocalDevice.init();
+ mLocalDevices.add(mHdmiCecLocalDevice);
+
+ HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
+ hdmiPortInfos[0] =
+ new HdmiPortInfo(1, HdmiPortInfo.PORT_OUTPUT, 0x0000, true, false, false);
+ mNativeWrapper.setPortInfo(hdmiPortInfos);
+ mNativeWrapper.setPortConnectionStatus(1, true);
+
+ mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_BOOT_UP);
+ mTestLooper.dispatchAll();
+
+ // Simulate AudioManager's behavior and response when setDeviceVolumeBehavior is called
+ doAnswer(invocation -> {
+ setDeviceVolumeBehavior(invocation.getArgument(0), invocation.getArgument(1));
+ return null;
+ }).when(mAudioManager).setDeviceVolumeBehavior(any(), anyInt());
+
+ // Set starting volume behavior
+ doReturn(AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE)
+ .when(mAudioManager).getDeviceVolumeBehavior(eq(getAudioOutputDevice()));
+
+ // Audio service always plays STREAM_MUSIC on the device we need
+ doReturn(Collections.singletonList(getAudioOutputDevice())).when(mAudioManager)
+ .getDevicesForAttributes(HdmiControlService.STREAM_MUSIC_ATTRIBUTES);
+
+ // Max volume of STREAM_MUSIC
+ doReturn(25).when(mAudioManager).getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+
+ // Receive messages from devices to make sure they're registered in HdmiCecNetwork
+ mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+ Constants.ADDR_TV, getLogicalAddress()));
+ if (getSystemAudioDeviceType() == DEVICE_AUDIO_SYSTEM) {
+ mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+ Constants.ADDR_AUDIO_SYSTEM, getLogicalAddress()));
+ }
+
+ mHdmiControlService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+ mHdmiControlService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+ mTestLooper.dispatchAll();
+ }
+
+ protected int getLogicalAddress() {
+ synchronized (mHdmiCecLocalDevice.mLock) {
+ return mHdmiCecLocalDevice.getDeviceInfo().getLogicalAddress();
+ }
+ }
+
+ /**
+ * Simulates the volume behavior of {@code device} being set to {@code behavior}.
+ */
+ protected void setDeviceVolumeBehavior(AudioDeviceAttributes device,
+ @AudioManager.DeviceVolumeBehavior int behavior) {
+ doReturn(behavior).when(mAudioManager).getDeviceVolumeBehavior(eq(device));
+ mHdmiControlService.onDeviceVolumeBehaviorChanged(device, behavior);
+ mTestLooper.dispatchAll();
+ }
+
+ /**
+ * Changes the setting for CEC volume.
+ */
+ protected void setCecVolumeControlSetting(@HdmiControlManager.VolumeControl int setting) {
+ mHdmiControlService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, setting);
+ mTestLooper.dispatchAll();
+ }
+
+ /**
+ * Has the device receive a <Report Features> message from the System Audio device specifying
+ * whether <Set Audio Volume Level> is supported or not.
+ */
+ protected void receiveSetAudioVolumeLevelSupport(
+ @DeviceFeatures.FeatureSupportStatus int featureSupportStatus) {
+ // <Report Features> can't specify an unknown feature support status
+ if (featureSupportStatus != DeviceFeatures.FEATURE_SUPPORT_UNKNOWN) {
+ mNativeWrapper.onCecMessage(ReportFeaturesMessage.build(
+ getSystemAudioDeviceLogicalAddress(), HdmiControlManager.HDMI_CEC_VERSION_2_0,
+ Arrays.asList(getSystemAudioDeviceType()), Constants.RC_PROFILE_SOURCE,
+ Collections.emptyList(),
+ DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder()
+ .setSetAudioVolumeLevelSupport(featureSupportStatus)
+ .build()));
+ mTestLooper.dispatchAll();
+ }
+ }
+
+ /**
+ * Enables System Audio mode if the System Audio device is an Audio System.
+ */
+ protected void enableSystemAudioModeIfNeeded() {
+ if (getSystemAudioDeviceType() == DEVICE_AUDIO_SYSTEM) {
+ receiveSetSystemAudioMode(true);
+ }
+ }
+
+ /**
+ * Sets System Audio Mode by having the device receive <Set System Audio Mode>
+ * from the Audio System.
+ */
+ protected void receiveSetSystemAudioMode(boolean status) {
+ mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildSetSystemAudioMode(
+ Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_BROADCAST, status));
+ mTestLooper.dispatchAll();
+ }
+
+ /**
+ * Has the device receive a <Report Audio Status> reporting the status in
+ * {@link #INITIAL_SYSTEM_AUDIO_DEVICE_STATUS}
+ */
+ protected void receiveInitialReportAudioStatus() {
+ receiveReportAudioStatus(
+ INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getVolume(),
+ INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getMute());
+ }
+
+ /**
+ * Has the device receive a <Report Audio Status> message from the System Audio Device.
+ */
+ protected void receiveReportAudioStatus(int volume, boolean mute) {
+ mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildReportAudioStatus(
+ getSystemAudioDeviceLogicalAddress(),
+ getLogicalAddress(),
+ volume,
+ mute));
+ mTestLooper.dispatchAll();
+ }
+
+ /**
+ * Triggers all the conditions required to enable Absolute Volume Control.
+ */
+ protected void enableAbsoluteVolumeControl() {
+ setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+ receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
+ enableSystemAudioModeIfNeeded();
+ receiveInitialReportAudioStatus();
+
+ verifyAbsoluteVolumeEnabled();
+ }
+
+ /**
+ * Verifies that AVC was enabled - that is the audio output device's volume behavior was last
+ * set to absolute volume behavior.
+ */
+ protected void verifyAbsoluteVolumeEnabled() {
+ InOrder inOrder = inOrder(mAudioManager, mAudioDeviceVolumeManager);
+ inOrder.verify(mAudioDeviceVolumeManager, atLeastOnce()).setDeviceAbsoluteVolumeBehavior(
+ eq(getAudioOutputDevice()), any(), any(), any(), anyBoolean());
+ inOrder.verify(mAudioManager, never()).setDeviceVolumeBehavior(
+ eq(getAudioOutputDevice()), not(eq(AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE)));
+ }
+
+ /**
+ * Verifies that AVC was disabled - that is, the audio output device's volume behavior was
+ * last set to something other than absolute volume behavior.
+ */
+ protected void verifyAbsoluteVolumeDisabled() {
+ InOrder inOrder = inOrder(mAudioManager, mAudioDeviceVolumeManager);
+ inOrder.verify(mAudioManager, atLeastOnce()).setDeviceVolumeBehavior(
+ eq(getAudioOutputDevice()), not(eq(AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE)));
+ inOrder.verify(mAudioDeviceVolumeManager, never()).setDeviceAbsoluteVolumeBehavior(
+ eq(getAudioOutputDevice()), any(), any(), any(), anyBoolean());
+ }
+
+ protected void verifyGiveAudioStatusNeverSent() {
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(
+ HdmiCecMessageBuilder.buildGiveAudioStatus(
+ getLogicalAddress(), getSystemAudioDeviceLogicalAddress()));
+ }
+
+ protected void verifyGiveAudioStatusSent() {
+ assertThat(mNativeWrapper.getResultMessages()).contains(
+ HdmiCecMessageBuilder.buildGiveAudioStatus(
+ getLogicalAddress(), getSystemAudioDeviceLogicalAddress()));
+ }
+
+ @Test
+ public void allConditionsExceptSavlSupportMet_sendsSetAudioVolumeLevelAndGiveFeatures() {
+ setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+ enableSystemAudioModeIfNeeded();
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(
+ SetAudioVolumeLevelMessage.build(
+ getLogicalAddress(), getSystemAudioDeviceLogicalAddress(),
+ Constants.AUDIO_VOLUME_STATUS_UNKNOWN));
+ assertThat(mNativeWrapper.getResultMessages()).contains(
+ HdmiCecMessageBuilder.buildGiveFeatures(
+ getLogicalAddress(), getSystemAudioDeviceLogicalAddress()));
+ }
+
+ @Test
+ public void allConditionsMet_savlSupportLast_reportFeatures_giveAudioStatusSent() {
+ setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+ enableSystemAudioModeIfNeeded();
+ verifyGiveAudioStatusNeverSent();
+
+ receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
+ verifyGiveAudioStatusSent();
+ }
+
+ @Test
+ public void allConditionsMet_savlSupportLast_noFeatureAbort_giveAudioStatusSent() {
+ setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+ enableSystemAudioModeIfNeeded();
+ verifyGiveAudioStatusNeverSent();
+
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+ verifyGiveAudioStatusSent();
+ }
+
+ @Test
+ public void allConditionsMet_cecVolumeEnabledLast_giveAudioStatusSent() {
+ setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ enableSystemAudioModeIfNeeded();
+ receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
+ verifyGiveAudioStatusNeverSent();
+
+ setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+ verifyGiveAudioStatusSent();
+ }
+
+ @Test
+ public void allConditionsMet_fullVolumeBehaviorLast_giveAudioStatusSent() {
+ setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+ enableSystemAudioModeIfNeeded();
+ receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
+ verifyGiveAudioStatusNeverSent();
+
+ setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ verifyGiveAudioStatusSent();
+ }
+
+ @Test
+ public void allConditionsMet_systemAudioModeEnabledLast_giveAudioStatusSent() {
+ // Only run when the System Audio device is an Audio System.
+ assume().that(getSystemAudioDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+
+ setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+ receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
+ verifyGiveAudioStatusNeverSent();
+
+ receiveSetSystemAudioMode(true);
+ verifyGiveAudioStatusSent();
+ }
+
+ @Test
+ public void giveAudioStatusSent_systemAudioDeviceSendsReportAudioStatus_avcEnabled() {
+ setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+ enableSystemAudioModeIfNeeded();
+ receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
+ setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+
+ // Verify that AVC was never enabled
+ verify(mAudioDeviceVolumeManager, never()).setDeviceAbsoluteVolumeBehavior(
+ eq(getAudioOutputDevice()), any(), any(), any(), anyBoolean());
+ receiveInitialReportAudioStatus();
+
+ verifyAbsoluteVolumeEnabled();
+ }
+
+ @Test
+ public void avcEnabled_cecVolumeDisabled_absoluteVolumeDisabled() {
+ enableAbsoluteVolumeControl();
+
+ setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_DISABLED);
+ verifyAbsoluteVolumeDisabled();
+ }
+
+ @Test
+ public void avcEnabled_setAudioVolumeLevelNotSupported_absoluteVolumeDisabled() {
+ enableAbsoluteVolumeControl();
+
+ receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_NOT_SUPPORTED);
+ verifyAbsoluteVolumeDisabled();
+ }
+
+ @Test
+ public void avcEnabled_setAudioVolumeLevelFeatureAborted_absoluteVolumeDisabled() {
+ enableAbsoluteVolumeControl();
+
+ mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ getSystemAudioDeviceLogicalAddress(), getLogicalAddress(),
+ Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL, Constants.ABORT_UNRECOGNIZED_OPCODE));
+ mTestLooper.dispatchAll();
+ verifyAbsoluteVolumeDisabled();
+ }
+
+ @Test
+ public void avcEnabled_systemAudioModeDisabled_absoluteVolumeDisabled() {
+ // Only run when the System Audio device is an Audio System.
+ assume().that(getSystemAudioDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+
+ enableAbsoluteVolumeControl();
+
+ receiveSetSystemAudioMode(false);
+ verifyAbsoluteVolumeDisabled();
+ }
+
+ @Test
+ public void avcEnabled_receiveReportAudioStatus_notifiesVolumeOrMuteChanges() {
+ // Initial <Report Audio Status> has volume=50 and mute=false
+ enableAbsoluteVolumeControl();
+
+ // New volume and mute status: sets both
+ receiveReportAudioStatus(20, true);
+ verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(5),
+ anyInt());
+ verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ eq(AudioManager.ADJUST_MUTE), anyInt());
+ clearInvocations(mAudioManager);
+
+ // New volume only: sets volume only
+ receiveReportAudioStatus(32, true);
+ verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
+ anyInt());
+ verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ eq(AudioManager.ADJUST_MUTE), anyInt());
+ clearInvocations(mAudioManager);
+
+ // New mute status only: sets mute only
+ receiveReportAudioStatus(32, false);
+ verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
+ anyInt());
+ verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ eq(AudioManager.ADJUST_UNMUTE), anyInt());
+ clearInvocations(mAudioManager);
+
+ // Repeat of earlier message: sets neither volume nor mute
+ receiveReportAudioStatus(32, false);
+ verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
+ anyInt());
+ verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ eq(AudioManager.ADJUST_UNMUTE), anyInt());
+ clearInvocations(mAudioManager);
+
+ // If AudioService causes us to send <Set Audio Volume Level>, the System Audio device's
+ // volume changes. Afterward, a duplicate of an earlier <Report Audio Status> should
+ // still cause us to call setStreamVolume()
+ mHdmiControlService.getAbsoluteVolumeChangedListener().onAudioDeviceVolumeChanged(
+ getAudioOutputDevice(),
+ new VolumeInfo.Builder(ENABLE_AVC_VOLUME_INFO)
+ .setVolumeIndex(20)
+ .build()
+ );
+ mTestLooper.dispatchAll();
+ receiveReportAudioStatus(32, false);
+ verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
+ anyInt());
+ verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ eq(AudioManager.ADJUST_UNMUTE), anyInt());
+ }
+
+ @Test
+ public void avcEnabled_audioDeviceVolumeAdjusted_sendsUserControlPressedAndGiveAudioStatus() {
+ enableAbsoluteVolumeControl();
+ mNativeWrapper.clearResultMessages();
+
+ mHdmiControlService.getAbsoluteVolumeChangedListener().onAudioDeviceVolumeAdjusted(
+ getAudioOutputDevice(),
+ ENABLE_AVC_VOLUME_INFO,
+ AudioManager.ADJUST_RAISE,
+ AudioDeviceVolumeManager.ADJUST_MODE_NORMAL
+ );
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(
+ HdmiCecMessageBuilder.buildUserControlPressed(getLogicalAddress(),
+ getSystemAudioDeviceLogicalAddress(), CEC_KEYCODE_VOLUME_UP));
+ assertThat(mNativeWrapper.getResultMessages()).contains(
+ HdmiCecMessageBuilder.buildUserControlReleased(getLogicalAddress(),
+ getSystemAudioDeviceLogicalAddress()));
+ assertThat(mNativeWrapper.getResultMessages()).contains(
+ HdmiCecMessageBuilder.buildGiveAudioStatus(getLogicalAddress(),
+ getSystemAudioDeviceLogicalAddress()));
+ }
+
+ @Test
+ public void avcEnabled_audioDeviceVolumeChanged_sendsSetAudioVolumeLevel() {
+ enableAbsoluteVolumeControl();
+ mNativeWrapper.clearResultMessages();
+
+ mHdmiControlService.getAbsoluteVolumeChangedListener().onAudioDeviceVolumeChanged(
+ getAudioOutputDevice(),
+ new VolumeInfo.Builder(ENABLE_AVC_VOLUME_INFO)
+ .setVolumeIndex(20)
+ .build()
+ );
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(
+ SetAudioVolumeLevelMessage.build(getLogicalAddress(),
+ getSystemAudioDeviceLogicalAddress(), 20));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java
index 5cec8ad1e63d..28ba4bb503f9 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java
@@ -56,7 +56,7 @@ public class DetectTvSystemAudioModeSupportActionTest {
mDeviceInfoForTests = HdmiDeviceInfo.hardwarePort(1001, 1234);
HdmiControlService hdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
- Collections.emptyList()) {
+ Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
void sendCecCommand(
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
index 52a0b6cdc2be..545f3183ee26 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
@@ -16,6 +16,7 @@
package com.android.server.hdmi;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
import static com.android.server.hdmi.Constants.ADDR_TV;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
@@ -78,7 +79,8 @@ public class DevicePowerStatusActionTest {
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
- mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList()) {
+ mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(),
+ new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
AudioManager getAudioManager() {
return new AudioManager() {
@@ -110,6 +112,7 @@ public class DevicePowerStatusActionTest {
mHdmiControlService.setCecController(hdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.initService();
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
mHdmiControlService.setPowerManager(mPowerManager);
mPhysicalAddress = 0x2000;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
index 35432edfcf16..d7fef90456ab 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
@@ -20,6 +20,7 @@ import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_ON;
import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_STANDBY;
import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_3;
@@ -100,7 +101,7 @@ public class DeviceSelectActionFromPlaybackTest {
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
- Collections.emptyList()) {
+ Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
boolean isControlEnabled() {
return true;
@@ -136,6 +137,7 @@ public class DeviceSelectActionFromPlaybackTest {
mLocalDevices.add(mHdmiCecLocalDevicePlayback);
mHdmiControlService.initService();
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mNativeWrapper.setPhysicalAddress(0x0000);
mPowerManager = new FakePowerManagerWrapper(context);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
index e77cd91b46d8..72d36b00d73d 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
@@ -20,6 +20,7 @@ import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_ON;
import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_STANDBY;
import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
import static com.android.server.hdmi.Constants.ADDR_TV;
@@ -109,7 +110,7 @@ public class DeviceSelectActionFromTvTest {
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
- Collections.emptyList()) {
+ Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
boolean isControlEnabled() {
return true;
@@ -145,6 +146,7 @@ public class DeviceSelectActionFromTvTest {
true, false, false);
mNativeWrapper.setPortInfo(hdmiPortInfos);
mHdmiControlService.initService();
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(context);
mHdmiControlService.setPowerManager(mPowerManager);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioDeviceVolumeManagerWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioDeviceVolumeManagerWrapper.java
new file mode 100644
index 000000000000..d33ef9bc8879
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioDeviceVolumeManagerWrapper.java
@@ -0,0 +1,72 @@
+/*
+ * 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.server.hdmi;
+
+import static android.media.AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener;
+import static android.media.AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceVolumeManager;
+import android.media.AudioManager;
+import android.media.VolumeInfo;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+/**
+ * Wrapper for {@link AudioDeviceVolumeManager} that stubs its methods. Useful for testing.
+ */
+public class FakeAudioDeviceVolumeManagerWrapper implements
+ AudioDeviceVolumeManagerWrapperInterface {
+
+ private final Set<OnDeviceVolumeBehaviorChangedListener> mVolumeBehaviorListeners;
+
+ public FakeAudioDeviceVolumeManagerWrapper() {
+ mVolumeBehaviorListeners = new HashSet<>();
+ }
+
+ @Override
+ public void addOnDeviceVolumeBehaviorChangedListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnDeviceVolumeBehaviorChangedListener listener)
+ throws SecurityException {
+ mVolumeBehaviorListeners.add(listener);
+ }
+
+ @Override
+ public void removeOnDeviceVolumeBehaviorChangedListener(
+ @NonNull OnDeviceVolumeBehaviorChangedListener listener) {
+ mVolumeBehaviorListeners.remove(listener);
+ }
+
+ @Override
+ public void setDeviceAbsoluteVolumeBehavior(
+ @NonNull AudioDeviceAttributes device,
+ @NonNull VolumeInfo volume,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnAudioDeviceVolumeChangedListener vclistener,
+ boolean handlesVolumeAdjustment) {
+ // Notify all volume behavior listeners that the device adopted absolute volume behavior
+ for (OnDeviceVolumeBehaviorChangedListener listener : mVolumeBehaviorListeners) {
+ listener.onDeviceVolumeBehaviorChanged(device,
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
index 30bcc7e8afa1..9f744f9373ed 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
@@ -89,7 +89,8 @@ public class HdmiCecAtomLoggingTest {
mContextSpy = spy(new ContextWrapper(
InstrumentationRegistry.getInstrumentation().getTargetContext()));
- mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList()));
+ mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList(),
+ new FakeAudioDeviceVolumeManagerWrapper()));
doNothing().when(mHdmiControlServiceSpy)
.writeStringSystemProperty(anyString(), anyString());
doReturn(mHdmiCecAtomWriterSpy).when(mHdmiControlServiceSpy).getAtomWriter();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
index 2dcc449e36a5..0cba10669c85 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
@@ -101,7 +101,8 @@ public class HdmiCecControllerTest {
mMyLooper = mTestLooper.getLooper();
mHdmiControlServiceSpy = spy(new HdmiControlService(
- InstrumentationRegistry.getTargetContext(), Collections.emptyList()));
+ InstrumentationRegistry.getTargetContext(), Collections.emptyList(),
+ new FakeAudioDeviceVolumeManagerWrapper()));
doReturn(mMyLooper).when(mHdmiControlServiceSpy).getIoLooper();
doReturn(mMyLooper).when(mHdmiControlServiceSpy).getServiceLooper();
doAnswer(__ -> mCecVersion).when(mHdmiControlServiceSpy).getCecVersion();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 70bc460411c8..91d265c81083 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -88,7 +88,7 @@ public class HdmiCecLocalDeviceAudioSystemTest {
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
- Collections.emptyList()) {
+ Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
AudioManager getAudioManager() {
return new AudioManager() {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 86130daf4aac..484b5a8dae1a 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -15,6 +15,7 @@
*/
package com.android.server.hdmi;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ABORT_UNRECOGNIZED_OPCODE;
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
@@ -92,7 +93,7 @@ public class HdmiCecLocalDevicePlaybackTest {
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
- Collections.emptyList()) {
+ Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
void wakeUp() {
mWokenUp = true;
@@ -151,6 +152,7 @@ public class HdmiCecLocalDevicePlaybackTest {
mNativeWrapper.setPortInfo(hdmiPortInfos);
mNativeWrapper.setPortConnectionStatus(1, true);
mHdmiControlService.initService();
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(context);
mHdmiControlService.setPowerManager(mPowerManager);
mHdmiControlService.setPowerManagerInternal(mPowerManagerInternal);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
index fb8baa30e6b4..48e70fe4a60f 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
@@ -17,6 +17,7 @@ package com.android.server.hdmi;
import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_TV;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
import static com.android.server.hdmi.Constants.ADDR_TV;
@@ -137,7 +138,8 @@ public class HdmiCecLocalDeviceTest {
Context context = InstrumentationRegistry.getTargetContext();
mHdmiControlService =
- new HdmiControlService(context, Collections.emptyList()) {
+ new HdmiControlService(context, Collections.emptyList(),
+ new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
boolean isControlEnabled() {
return isControlEnabled;
@@ -190,6 +192,7 @@ public class HdmiCecLocalDeviceTest {
mNativeWrapper.setPortInfo(hdmiPortInfos);
mNativeWrapper.setPortConnectionStatus(1, true);
mHdmiControlService.initService();
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mNativeWrapper.setPhysicalAddress(0x2000);
mTestLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index df4aa5dac9df..f27b8c2f4b3a 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -15,6 +15,7 @@
*/
package com.android.server.hdmi;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ABORT_UNRECOGNIZED_OPCODE;
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
@@ -29,8 +30,10 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.content.Context;
import android.hardware.hdmi.HdmiControlManager;
@@ -125,7 +128,7 @@ public class HdmiCecLocalDeviceTvTest {
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
- Collections.emptyList()) {
+ Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
void wakeUp() {
mWokenUp = true;
@@ -179,6 +182,7 @@ public class HdmiCecLocalDeviceTvTest {
new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true);
mNativeWrapper.setPortInfo(hdmiPortInfos);
mHdmiControlService.initService();
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(context);
mHdmiControlService.setPowerManager(mPowerManager);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
@@ -768,7 +772,7 @@ public class HdmiCecLocalDeviceTvTest {
// When the device reports its physical address, the listener eventually is invoked.
HdmiCecMessage reportPhysicalAddress =
HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
- ADDR_PLAYBACK_2, 0x1000, HdmiDeviceInfo.DEVICE_PLAYBACK);
+ ADDR_PLAYBACK_2, 0x1000, HdmiDeviceInfo.DEVICE_PLAYBACK);
mNativeWrapper.onCecMessage(reportPhysicalAddress);
mTestLooper.dispatchAll();
@@ -777,6 +781,54 @@ public class HdmiCecLocalDeviceTvTest {
assertThat(mDeviceEventListeners.size()).isEqualTo(1);
assertThat(mDeviceEventListeners.get(0).getStatus())
.isEqualTo(HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
+ }
+
+ @Test
+ public void receiveSetAudioVolumeLevel_samNotActivated_noFeatureAbort_volumeChanges() {
+ when(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)).thenReturn(25);
+
+ // Max volume of STREAM_MUSIC is retrieved on boot
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
+ mTestLooper.dispatchAll();
+ mNativeWrapper.onCecMessage(SetAudioVolumeLevelMessage.build(
+ ADDR_PLAYBACK_1,
+ ADDR_TV,
+ 20));
+ mTestLooper.dispatchAll();
+
+ // <Feature Abort>[Not in correct mode] not sent
+ HdmiCecMessage featureAbortMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ ADDR_TV,
+ ADDR_PLAYBACK_1,
+ Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL,
+ Constants.ABORT_NOT_IN_CORRECT_MODE);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbortMessage);
+
+ // <Set Audio Volume Level> uses volume range [0, 100]; STREAM_MUSIC uses range [0, 25]
+ verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(5), anyInt());
+ }
+
+ @Test
+ public void receiveSetAudioVolumeLevel_samActivated_respondsFeatureAbort_noVolumeChange() {
+ mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildSetSystemAudioMode(
+ ADDR_AUDIO_SYSTEM, ADDR_TV, true));
+ mTestLooper.dispatchAll();
+
+ mNativeWrapper.onCecMessage(SetAudioVolumeLevelMessage.build(
+ ADDR_PLAYBACK_1, ADDR_TV, 50));
+ mTestLooper.dispatchAll();
+
+ // <Feature Abort>[Not in correct mode] sent
+ HdmiCecMessage featureAbortMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ ADDR_TV,
+ ADDR_PLAYBACK_1,
+ Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL,
+ Constants.ABORT_NOT_IN_CORRECT_MODE);
+ assertThat(mNativeWrapper.getResultMessages()).contains(featureAbortMessage);
+
+ // AudioManager not notified of volume change
+ verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), anyInt(),
+ anyInt());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
index 50c9f70ccb03..a446e109c921 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
@@ -51,7 +51,8 @@ public class HdmiCecMessageValidatorTest {
@Before
public void setUp() throws Exception {
HdmiControlService mHdmiControlService = new HdmiControlService(
- InstrumentationRegistry.getTargetContext(), Collections.emptyList());
+ InstrumentationRegistry.getTargetContext(), Collections.emptyList(),
+ new FakeAudioDeviceVolumeManagerWrapper());
mHdmiControlService.setIoLooper(mTestLooper.getLooper());
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
index 03532ae1cb1f..b8a1ba363373 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
@@ -67,7 +67,8 @@ public class HdmiCecNetworkTest {
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getTargetContext();
- mHdmiControlService = new HdmiControlService(mContext, Collections.emptyList()) {
+ mHdmiControlService = new HdmiControlService(mContext, Collections.emptyList(),
+ new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
mDeviceEventListenerStatuses.add(status);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
index 7a68285bc003..b94deeddb8af 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
@@ -15,6 +15,7 @@
*/
package com.android.server.hdmi;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.google.common.truth.Truth.assertThat;
@@ -65,7 +66,8 @@ public class HdmiCecPowerStatusControllerTest {
Context contextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
Looper myLooper = mTestLooper.getLooper();
- mHdmiControlService = new HdmiControlService(contextSpy, Collections.emptyList()) {
+ mHdmiControlService = new HdmiControlService(contextSpy, Collections.emptyList(),
+ new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
boolean isControlEnabled() {
return true;
@@ -105,6 +107,7 @@ public class HdmiCecPowerStatusControllerTest {
mNativeWrapper.setPortInfo(hdmiPortInfos);
mNativeWrapper.setPortConnectionStatus(1, true);
mHdmiControlService.initService();
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(contextSpy);
mHdmiControlService.setPowerManager(mPowerManager);
mHdmiControlService.getHdmiCecNetwork().initPortInfo();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 3987c32277cd..6266571d33d4 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -91,7 +91,8 @@ public class HdmiControlServiceTest {
HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
- mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList()));
+ mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList(),
+ new FakeAudioDeviceVolumeManagerWrapper()));
doNothing().when(mHdmiControlServiceSpy)
.writeStringSystemProperty(anyString(), anyString());
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
index 561e6a5fec41..46a4e862c300 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
@@ -16,6 +16,7 @@
package com.android.server.hdmi;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_TV;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.android.server.hdmi.OneTouchPlayAction.STATE_WAITING_FOR_REPORT_POWER_STATUS;
@@ -87,7 +88,8 @@ public class OneTouchPlayActionTest {
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
mHdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
- mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList()) {
+ mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(),
+ new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
AudioManager getAudioManager() {
return new AudioManager() {
@@ -120,6 +122,7 @@ public class OneTouchPlayActionTest {
mHdmiControlService.setCecController(hdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.initService();
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
mHdmiControlService.setPowerManager(mPowerManager);
mPhysicalAddress = 0x2000;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToAudioSystemAvcTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToAudioSystemAvcTest.java
new file mode 100644
index 000000000000..64186028e6ed
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToAudioSystemAvcTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.server.hdmi;
+
+import android.hardware.hdmi.DeviceFeatures;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.media.AudioDeviceAttributes;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.google.android.collect.Lists;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Arrays;
+
+/**
+ * Tests for Absolute Volume Control where the local device is a Playback device and the
+ * System Audio device is an Audio System.
+ */
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class PlaybackDeviceToAudioSystemAvcTest extends BaseAbsoluteVolumeControlTest {
+
+ @Override
+ protected HdmiCecLocalDevice createLocalDevice(HdmiControlService hdmiControlService) {
+ return new HdmiCecLocalDevicePlayback(hdmiControlService);
+ }
+
+ @Override
+ protected int getPhysicalAddress() {
+ return 0x1100;
+ }
+
+ @Override
+ protected int getDeviceType() {
+ return HdmiDeviceInfo.DEVICE_PLAYBACK;
+ }
+
+ @Override
+ protected AudioDeviceAttributes getAudioOutputDevice() {
+ return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI;
+ }
+
+ @Override
+ protected int getSystemAudioDeviceLogicalAddress() {
+ return Constants.ADDR_AUDIO_SYSTEM;
+ }
+
+ @Override
+ protected int getSystemAudioDeviceType() {
+ return HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM;
+ }
+
+ /**
+ * AVC is disabled if the Audio System disables System Audio mode, and the TV has unknown
+ * support for <Set Audio Volume Level>. It is enabled once the TV confirms support for
+ * <Set Audio Volume Level> and sends <Report Audio Status>.
+ */
+ @Test
+ public void switchToTv_absoluteVolumeControlDisabledUntilAllConditionsMet() {
+ enableAbsoluteVolumeControl();
+
+ // Audio System disables System Audio Mode. AVC should be disabled.
+ receiveSetSystemAudioMode(false);
+ verifyAbsoluteVolumeDisabled();
+
+ // TV reports support for <Set Audio Volume Level>
+ mNativeWrapper.onCecMessage(ReportFeaturesMessage.build(
+ Constants.ADDR_TV, HdmiControlManager.HDMI_CEC_VERSION_2_0,
+ Arrays.asList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV,
+ Lists.newArrayList(Constants.RC_PROFILE_TV_NONE),
+ DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder()
+ .setSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED)
+ .build()));
+ mTestLooper.dispatchAll();
+
+ // TV reports its initial audio status
+ mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildReportAudioStatus(
+ Constants.ADDR_TV,
+ getLogicalAddress(),
+ 30,
+ false));
+ mTestLooper.dispatchAll();
+
+ verifyAbsoluteVolumeEnabled();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToTvAvcTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToTvAvcTest.java
new file mode 100644
index 000000000000..504c3bc2626a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToTvAvcTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.server.hdmi;
+
+import static org.mockito.Mockito.clearInvocations;
+
+import android.hardware.hdmi.DeviceFeatures;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.media.AudioDeviceAttributes;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+/**
+ * Tests for Absolute Volume Control where the local device is a Playback device and the
+ * System Audio device is a TV.
+ */
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class PlaybackDeviceToTvAvcTest extends BaseAbsoluteVolumeControlTest {
+
+ @Override
+ protected HdmiCecLocalDevice createLocalDevice(HdmiControlService hdmiControlService) {
+ return new HdmiCecLocalDevicePlayback(hdmiControlService);
+ }
+
+ @Override
+ protected int getPhysicalAddress() {
+ return 0x1100;
+ }
+
+ @Override
+ protected int getDeviceType() {
+ return HdmiDeviceInfo.DEVICE_PLAYBACK;
+ }
+
+ @Override
+ protected AudioDeviceAttributes getAudioOutputDevice() {
+ return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI;
+ }
+
+ @Override
+ protected int getSystemAudioDeviceLogicalAddress() {
+ return Constants.ADDR_TV;
+ }
+
+ @Override
+ protected int getSystemAudioDeviceType() {
+ return HdmiDeviceInfo.DEVICE_TV;
+ }
+
+ /**
+ * AVC is disabled when an Audio System with unknown support for <Set Audio Volume Level>
+ * becomes the System Audio device. It is enabled once the Audio System reports that it
+ * supports <Set Audio Volume Level> and sends <Report Audio Status>.
+ */
+ @Test
+ public void switchToAudioSystem_absoluteVolumeControlDisabledUntilAllConditionsMet() {
+ enableAbsoluteVolumeControl();
+
+ // Audio System enables System Audio Mode. AVC should be disabled.
+ receiveSetSystemAudioMode(true);
+ verifyAbsoluteVolumeDisabled();
+
+ clearInvocations(mAudioManager, mAudioDeviceVolumeManager);
+
+ // Audio System reports support for <Set Audio Volume Level>
+ mNativeWrapper.onCecMessage(ReportFeaturesMessage.build(
+ Constants.ADDR_AUDIO_SYSTEM, HdmiControlManager.HDMI_CEC_VERSION_2_0,
+ Arrays.asList(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM), Constants.RC_PROFILE_SOURCE,
+ Collections.emptyList(),
+ DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder()
+ .setSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED)
+ .build()));
+ mTestLooper.dispatchAll();
+
+ // Audio system reports its initial audio status
+ mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildReportAudioStatus(
+ Constants.ADDR_AUDIO_SYSTEM,
+ getLogicalAddress(),
+ 30,
+ false));
+ mTestLooper.dispatchAll();
+
+ verifyAbsoluteVolumeEnabled();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
index c878f99f7912..e5058bea8d1b 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
@@ -16,6 +16,7 @@
package com.android.server.hdmi;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
@@ -68,7 +69,8 @@ public class PowerStatusMonitorActionTest {
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
mHdmiControlService = new HdmiControlService(mContextSpy,
- Collections.singletonList(HdmiDeviceInfo.DEVICE_TV)) {
+ Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
+ new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
AudioManager getAudioManager() {
return new AudioManager() {
@@ -110,6 +112,7 @@ public class PowerStatusMonitorActionTest {
new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, false);
mNativeWrapper.setPortInfo(hdmiPortInfo);
mHdmiControlService.initService();
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
mHdmiControlService.setPowerManager(mPowerManager);
mPhysicalAddress = 0x0000;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
index 6184c2116e1d..f7983ca21816 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
@@ -16,6 +16,7 @@
package com.android.server.hdmi;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
@@ -96,8 +97,8 @@ public class RequestSadActionTest {
mMyLooper = mTestLooper.getLooper();
mHdmiControlService =
- new HdmiControlService(context,
- Collections.emptyList()) {
+ new HdmiControlService(context, Collections.emptyList(),
+ new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
boolean isControlEnabled() {
return true;
@@ -125,6 +126,7 @@ public class RequestSadActionTest {
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mLocalDevices.add(mHdmiCecLocalDeviceTv);
mHdmiControlService.initService();
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(context);
mHdmiControlService.setPowerManager(mPowerManager);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
index 0587864eeb20..566a7e0fecce 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
@@ -16,6 +16,7 @@
package com.android.server.hdmi;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
@@ -149,7 +150,7 @@ public class RoutingControlActionTest {
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext(),
- Collections.emptyList()) {
+ Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
boolean isControlEnabled() {
return true;
@@ -186,6 +187,7 @@ public class RoutingControlActionTest {
true, false, false);
mNativeWrapper.setPortInfo(hdmiPortInfos);
mHdmiControlService.initService();
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(context);
mHdmiControlService.setPowerManager(mPowerManager);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
index a34b55c00308..087e407e314c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
@@ -20,6 +20,7 @@ import static android.hardware.hdmi.DeviceFeatures.FEATURE_NOT_SUPPORTED;
import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED;
import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORT_UNKNOWN;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.google.common.truth.Truth.assertThat;
@@ -81,7 +82,8 @@ public class SetAudioVolumeLevelDiscoveryActionTest {
mContextSpy = spy(new ContextWrapper(
InstrumentationRegistry.getInstrumentation().getTargetContext()));
- mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList()));
+ mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList(),
+ new FakeAudioDeviceVolumeManagerWrapper()));
doNothing().when(mHdmiControlServiceSpy)
.writeStringSystemProperty(anyString(), anyString());
@@ -98,6 +100,7 @@ public class SetAudioVolumeLevelDiscoveryActionTest {
mHdmiControlServiceSpy.setHdmiMhlController(
HdmiMhlControllerStub.create(mHdmiControlServiceSpy));
mHdmiControlServiceSpy.initService();
+ mHdmiControlServiceSpy.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
mHdmiControlServiceSpy.setPowerManager(mPowerManager);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
index 9d143418fd4c..1644252e5739 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
@@ -17,6 +17,7 @@
package com.android.server.hdmi;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.android.server.hdmi.SystemAudioAutoInitiationAction.RETRIES_ON_TIMEOUT;
@@ -69,7 +70,8 @@ public class SystemAudioAutoInitiationActionTest {
Looper myLooper = mTestLooper.getLooper();
- mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList()) {
+ mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(),
+ new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
AudioManager getAudioManager() {
return new AudioManager() {
@@ -108,6 +110,7 @@ public class SystemAudioAutoInitiationActionTest {
new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true);
mNativeWrapper.setPortInfo(hdmiPortInfos);
mHdmiControlService.initService();
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
mPowerManager = new FakePowerManagerWrapper(mContextSpy);
mHdmiControlService.setPowerManager(mPowerManager);
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
index 095c69c776a2..c2f706ad1220 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
@@ -69,7 +69,7 @@ public class SystemAudioInitiationActionFromAvrTest {
Context context = InstrumentationRegistry.getTargetContext();
HdmiControlService hdmiControlService = new HdmiControlService(context,
- Collections.emptyList()) {
+ Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
@Override
void sendCecCommand(
HdmiCecMessage command, @Nullable SendMessageCallback callback) {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvcTest.java b/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvcTest.java
new file mode 100644
index 000000000000..41c0e0d29879
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvcTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.server.hdmi;
+
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.media.AudioDeviceAttributes;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for Absolute Volume Control where the local device is a TV and the System Audio device
+ * is an Audio System. Assumes that the TV uses ARC (rather than eARC).
+ */
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class TvToAudioSystemAvcTest extends BaseAbsoluteVolumeControlTest {
+
+ @Override
+ protected HdmiCecLocalDevice createLocalDevice(HdmiControlService hdmiControlService) {
+ return new HdmiCecLocalDeviceTv(hdmiControlService);
+ }
+
+ @Override
+ protected int getPhysicalAddress() {
+ return 0x0000;
+ }
+
+ @Override
+ protected int getDeviceType() {
+ return HdmiDeviceInfo.DEVICE_TV;
+ }
+
+ @Override
+ protected AudioDeviceAttributes getAudioOutputDevice() {
+ return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI_ARC;
+ }
+
+ @Override
+ protected int getSystemAudioDeviceLogicalAddress() {
+ return Constants.ADDR_AUDIO_SYSTEM;
+ }
+
+ @Override
+ protected int getSystemAudioDeviceType() {
+ return HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
index bd35be4d0f3e..c735d1868a62 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
@@ -22,7 +22,6 @@ import static junit.framework.Assert.assertNull;
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
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.doNothing;
@@ -39,7 +38,6 @@ import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
import android.os.Binder;
import android.os.HandlerThread;
import android.os.LocaleList;
@@ -102,8 +100,6 @@ public class LocaleManagerBackupRestoreTest {
@Mock
private Context mMockContext;
@Mock
- private PackageManagerInternal mMockPackageManagerInternal;
- @Mock
private PackageManager mMockPackageManager;
@Mock
private LocaleManagerService mMockLocaleManagerService;
@@ -129,7 +125,6 @@ public class LocaleManagerBackupRestoreTest {
@Before
public void setUp() throws Exception {
mMockContext = mock(Context.class);
- mMockPackageManagerInternal = mock(PackageManagerInternal.class);
mMockPackageManager = mock(PackageManager.class);
mMockLocaleManagerService = mock(LocaleManagerService.class);
SystemAppUpdateTracker systemAppUpdateTracker = mock(SystemAppUpdateTracker.class);
@@ -141,7 +136,7 @@ public class LocaleManagerBackupRestoreTest {
broadcastHandlerThread.start();
mBackupHelper = spy(new ShadowLocaleManagerBackupHelper(mMockContext,
- mMockLocaleManagerService, mMockPackageManagerInternal, mClock, STAGE_DATA,
+ mMockLocaleManagerService, mMockPackageManager, mClock, STAGE_DATA,
broadcastHandlerThread));
doNothing().when(mBackupHelper).notifyBackupManager();
@@ -158,8 +153,8 @@ public class LocaleManagerBackupRestoreTest {
@Test
public void testBackupPayload_noAppsInstalled_returnsNull() throws Exception {
- doReturn(List.of()).when(mMockPackageManagerInternal)
- .getInstalledApplications(anyLong(), anyInt(), anyInt());
+ doReturn(List.of()).when(mMockPackageManager)
+ .getInstalledApplicationsAsUser(any(), anyInt());
assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
}
@@ -198,8 +193,8 @@ public class LocaleManagerBackupRestoreTest {
ApplicationInfo anotherAppInfo = new ApplicationInfo();
defaultAppInfo.packageName = DEFAULT_PACKAGE_NAME;
anotherAppInfo.packageName = "com.android.anotherapp";
- doReturn(List.of(defaultAppInfo, anotherAppInfo)).when(mMockPackageManagerInternal)
- .getInstalledApplications(anyLong(), anyInt(), anyInt());
+ doReturn(List.of(defaultAppInfo, anotherAppInfo)).when(mMockPackageManager)
+ .getInstalledApplicationsAsUser(any(), anyInt());
setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, DEFAULT_LOCALES);
// Exception when getting locales for anotherApp.
@@ -447,8 +442,8 @@ public class LocaleManagerBackupRestoreTest {
// Retention period has not elapsed.
setCurrentTimeMillis(
DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.minusHours(1).toMillis());
- doReturn(List.of()).when(mMockPackageManagerInternal)
- .getInstalledApplications(anyLong(), anyInt(), anyInt());
+ doReturn(List.of()).when(mMockPackageManager)
+ .getInstalledApplicationsAsUser(any(), anyInt());
assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
checkStageDataExists(DEFAULT_USER_ID);
@@ -456,8 +451,8 @@ public class LocaleManagerBackupRestoreTest {
// Exactly RETENTION_PERIOD amount of time has passed so stage data should still not be
// removed.
setCurrentTimeMillis(DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.toMillis());
- doReturn(List.of()).when(mMockPackageManagerInternal)
- .getInstalledApplications(anyLong(), anyInt(), anyInt());
+ doReturn(List.of()).when(mMockPackageManager)
+ .getInstalledApplicationsAsUser(any(), anyInt());
assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
checkStageDataExists(DEFAULT_USER_ID);
@@ -465,8 +460,8 @@ public class LocaleManagerBackupRestoreTest {
// Retention period has now expired, stage data should be deleted.
setCurrentTimeMillis(
DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.plusSeconds(1).toMillis());
- doReturn(List.of()).when(mMockPackageManagerInternal)
- .getInstalledApplications(anyLong(), anyInt(), anyInt());
+ doReturn(List.of()).when(mMockPackageManager)
+ .getInstalledApplicationsAsUser(any(), anyInt());
assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
checkStageDataDoesNotExist(DEFAULT_USER_ID);
@@ -577,8 +572,8 @@ public class LocaleManagerBackupRestoreTest {
private void setUpDummyAppForPackageManager(String packageName) {
ApplicationInfo dummyApp = new ApplicationInfo();
dummyApp.packageName = packageName;
- doReturn(List.of(dummyApp)).when(mMockPackageManagerInternal)
- .getInstalledApplications(anyLong(), anyInt(), anyInt());
+ doReturn(List.of(dummyApp)).when(mMockPackageManager)
+ .getInstalledApplicationsAsUser(any(), anyInt());
}
/**
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
index 0b3ef4542cbf..1dcdbac8a7c2 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
@@ -23,7 +23,6 @@ import static junit.framework.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
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.doNothing;
@@ -40,7 +39,6 @@ import android.content.Context;
import android.content.pm.InstallSourceInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
import android.os.Binder;
import android.os.LocaleList;
@@ -80,7 +78,7 @@ public class LocaleManagerServiceTest {
@Mock
private Context mMockContext;
@Mock
- private PackageManagerInternal mMockPackageManagerInternal;
+ private PackageManager mMockPackageManager;
@Mock
private FakePackageConfigurationUpdater mFakePackageConfigurationUpdater;
@Mock
@@ -95,14 +93,13 @@ public class LocaleManagerServiceTest {
mMockContext = mock(Context.class);
mMockActivityTaskManager = mock(ActivityTaskManagerInternal.class);
mMockActivityManager = mock(ActivityManagerInternal.class);
- mMockPackageManagerInternal = mock(PackageManagerInternal.class);
+ mMockPackageManager = mock(PackageManager.class);
mMockPackageMonitor = mock(PackageMonitor.class);
// For unit tests, set the default installer info
- PackageManager mockPackageManager = mock(PackageManager.class);
- doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mockPackageManager)
+ doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mMockPackageManager)
.getInstallSourceInfo(anyString());
- doReturn(mockPackageManager).when(mMockContext).getPackageManager();
+ doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
mFakePackageConfigurationUpdater = new FakePackageConfigurationUpdater();
doReturn(mFakePackageConfigurationUpdater)
@@ -117,14 +114,14 @@ public class LocaleManagerServiceTest {
mMockBackupHelper = mock(ShadowLocaleManagerBackupHelper.class);
mLocaleManagerService = new LocaleManagerService(mMockContext, mMockActivityTaskManager,
- mMockActivityManager, mMockPackageManagerInternal,
+ mMockActivityManager, mMockPackageManager,
mMockBackupHelper, mMockPackageMonitor);
}
@Test(expected = SecurityException.class)
public void testSetApplicationLocales_arbitraryAppWithoutPermissions_fails() throws Exception {
doReturn(DEFAULT_UID)
- .when(mMockPackageManagerInternal).getPackageUid(anyString(), anyLong(), anyInt());
+ .when(mMockPackageManager).getPackageUidAsUser(anyString(), any(), anyInt());
setUpFailingPermissionCheckFor(Manifest.permission.CHANGE_CONFIGURATION);
try {
@@ -170,7 +167,7 @@ public class LocaleManagerServiceTest {
@Test
public void testSetApplicationLocales_arbitraryAppWithPermission_succeeds() throws Exception {
doReturn(DEFAULT_UID)
- .when(mMockPackageManagerInternal).getPackageUid(anyString(), anyLong(), anyInt());
+ .when(mMockPackageManager).getPackageUidAsUser(anyString(), any(), anyInt());
// if package is not owned by the caller, the calling app should have the following
// permission. We will mock this to succeed to imitate that.
setUpPassingPermissionCheckFor(Manifest.permission.CHANGE_CONFIGURATION);
@@ -186,7 +183,7 @@ public class LocaleManagerServiceTest {
@Test
public void testSetApplicationLocales_callerOwnsPackage_succeeds() throws Exception {
doReturn(Binder.getCallingUid())
- .when(mMockPackageManagerInternal).getPackageUid(anyString(), anyLong(), anyInt());
+ .when(mMockPackageManager).getPackageUidAsUser(anyString(), any(), anyInt());
mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
DEFAULT_LOCALES);
@@ -197,8 +194,8 @@ public class LocaleManagerServiceTest {
@Test(expected = IllegalArgumentException.class)
public void testSetApplicationLocales_invalidPackageOrUserId_fails() throws Exception {
- doReturn(INVALID_UID)
- .when(mMockPackageManagerInternal).getPackageUid(anyString(), anyLong(), anyInt());
+ doThrow(new PackageManager.NameNotFoundException("Mock"))
+ .when(mMockPackageManager).getPackageUidAsUser(anyString(), any(), anyInt());
try {
mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
LocaleList.getEmptyLocaleList());
@@ -211,8 +208,8 @@ public class LocaleManagerServiceTest {
@Test(expected = SecurityException.class)
public void testGetApplicationLocales_arbitraryAppWithoutPermission_fails() throws Exception {
- doReturn(DEFAULT_UID).when(mMockPackageManagerInternal)
- .getPackageUid(anyString(), anyLong(), anyInt());
+ doReturn(DEFAULT_UID).when(mMockPackageManager)
+ .getPackageUidAsUser(anyString(), any(), anyInt());
setUpFailingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
try {
@@ -229,8 +226,8 @@ public class LocaleManagerServiceTest {
public void testGetApplicationLocales_appSpecificConfigAbsent_returnsEmptyList()
throws Exception {
// any valid app calling for its own package or having appropriate permission
- doReturn(DEFAULT_UID).when(mMockPackageManagerInternal)
- .getPackageUid(anyString(), anyLong(), anyInt());
+ doReturn(DEFAULT_UID).when(mMockPackageManager)
+ .getPackageUidAsUser(anyString(), any(), anyInt());
setUpPassingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
doReturn(null)
.when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
@@ -244,8 +241,8 @@ public class LocaleManagerServiceTest {
@Test
public void testGetApplicationLocales_appSpecificLocalesAbsent_returnsEmptyList()
throws Exception {
- doReturn(DEFAULT_UID).when(mMockPackageManagerInternal)
- .getPackageUid(anyString(), anyLong(), anyInt());
+ doReturn(DEFAULT_UID).when(mMockPackageManager)
+ .getPackageUidAsUser(anyString(), any(), anyInt());
setUpPassingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
doReturn(new PackageConfig(/* nightMode = */ 0, /* locales = */ null))
.when(mMockActivityTaskManager).getApplicationConfig(any(), anyInt());
@@ -259,8 +256,8 @@ public class LocaleManagerServiceTest {
@Test
public void testGetApplicationLocales_callerOwnsAppAndConfigPresent_returnsLocales()
throws Exception {
- doReturn(Binder.getCallingUid()).when(mMockPackageManagerInternal)
- .getPackageUid(anyString(), anyLong(), anyInt());
+ doReturn(Binder.getCallingUid()).when(mMockPackageManager)
+ .getPackageUidAsUser(anyString(), any(), anyInt());
doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
.when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
@@ -273,8 +270,8 @@ public class LocaleManagerServiceTest {
@Test
public void testGetApplicationLocales_arbitraryCallerWithPermissions_returnsLocales()
throws Exception {
- doReturn(DEFAULT_UID).when(mMockPackageManagerInternal)
- .getPackageUid(anyString(), anyLong(), anyInt());
+ doReturn(DEFAULT_UID).when(mMockPackageManager)
+ .getPackageUidAsUser(anyString(), any(), anyInt());
setUpPassingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
.when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
@@ -288,10 +285,10 @@ public class LocaleManagerServiceTest {
@Test
public void testGetApplicationLocales_callerIsInstaller_returnsLocales()
throws Exception {
- doReturn(DEFAULT_UID).when(mMockPackageManagerInternal)
- .getPackageUid(eq(DEFAULT_PACKAGE_NAME), anyLong(), anyInt());
- doReturn(Binder.getCallingUid()).when(mMockPackageManagerInternal)
- .getPackageUid(eq(DEFAULT_INSTALLER_PACKAGE_NAME), anyLong(), anyInt());
+ doReturn(DEFAULT_UID).when(mMockPackageManager)
+ .getPackageUidAsUser(eq(DEFAULT_PACKAGE_NAME), any(), anyInt());
+ doReturn(Binder.getCallingUid()).when(mMockPackageManager)
+ .getPackageUidAsUser(eq(DEFAULT_INSTALLER_PACKAGE_NAME), any(), anyInt());
doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
.when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
diff --git a/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java b/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
index ad9be0d5fe8d..e403c87f65d9 100644
--- a/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
+++ b/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
@@ -17,7 +17,7 @@
package com.android.server.locales;
import android.content.Context;
-import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageManager;
import android.os.HandlerThread;
import android.util.SparseArray;
@@ -31,9 +31,10 @@ import java.time.Clock;
public class ShadowLocaleManagerBackupHelper extends LocaleManagerBackupHelper {
ShadowLocaleManagerBackupHelper(Context context,
LocaleManagerService localeManagerService,
- PackageManagerInternal pmInternal, Clock clock,
+ PackageManager packageManager, Clock clock,
SparseArray<LocaleManagerBackupHelper.StagedData> stagedData,
HandlerThread broadcastHandlerThread) {
- super(context, localeManagerService, pmInternal, clock, stagedData, broadcastHandlerThread);
+ super(context, localeManagerService, packageManager, clock, stagedData,
+ broadcastHandlerThread);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
index db602ca83f30..808b74e31029 100644
--- a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
@@ -38,7 +38,6 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.InstallSourceInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
import android.os.Binder;
import android.os.Environment;
import android.os.LocaleList;
@@ -93,8 +92,6 @@ public class SystemAppUpdateTrackerTest {
@Mock
PackageManager mMockPackageManager;
@Mock
- private PackageManagerInternal mMockPackageManagerInternal;
- @Mock
private ActivityTaskManagerInternal mMockActivityTaskManager;
@Mock
private ActivityManagerInternal mMockActivityManager;
@@ -110,21 +107,20 @@ public class SystemAppUpdateTrackerTest {
mMockContext = mock(Context.class);
mMockActivityTaskManager = mock(ActivityTaskManagerInternal.class);
mMockActivityManager = mock(ActivityManagerInternal.class);
- mMockPackageManagerInternal = mock(PackageManagerInternal.class);
+ mMockPackageManager = mock(PackageManager.class);
LocaleManagerBackupHelper mockLocaleManagerBackupHelper =
mock(ShadowLocaleManagerBackupHelper.class);
// PackageMonitor is not needed in LocaleManagerService for these tests hence it is
// passed as null.
mLocaleManagerService = new LocaleManagerService(mMockContext,
mMockActivityTaskManager, mMockActivityManager,
- mMockPackageManagerInternal, mockLocaleManagerBackupHelper,
+ mMockPackageManager, mockLocaleManagerBackupHelper,
/* mPackageMonitor= */ null);
doReturn(DEFAULT_USER_ID).when(mMockActivityManager)
.handleIncomingUser(anyInt(), anyInt(), eq(DEFAULT_USER_ID), anyBoolean(), anyInt(),
anyString(), anyString());
- mMockPackageManager = mock(PackageManager.class);
doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mMockPackageManager)
.getInstallSourceInfo(anyString());
doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
index 004d7bc2707c..07cca0ca6ba0 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
@@ -84,15 +84,15 @@ import java.util.function.Function;
@RunWith(AndroidJUnit4.class)
public class PackageParserLegacyCoreTest {
private static final String RELEASED = null;
- private static final String OLDER_PRE_RELEASE = "A";
- private static final String PRE_RELEASE = "B";
- private static final String NEWER_PRE_RELEASE = "C";
+ private static final String OLDER_PRE_RELEASE = "Q";
+ private static final String PRE_RELEASE = "R";
+ private static final String NEWER_PRE_RELEASE = "Z";
// Codenames with a fingerprint attached to them. These may only be present in the apps
// declared min SDK and not as platform codenames.
- private static final String OLDER_PRE_RELEASE_WITH_FINGERPRINT = "A.fingerprint";
- private static final String PRE_RELEASE_WITH_FINGERPRINT = "B.fingerprint";
- private static final String NEWER_PRE_RELEASE_WITH_FINGERPRINT = "C.fingerprint";
+ private static final String OLDER_PRE_RELEASE_WITH_FINGERPRINT = "Q.fingerprint";
+ private static final String PRE_RELEASE_WITH_FINGERPRINT = "R.fingerprint";
+ private static final String NEWER_PRE_RELEASE_WITH_FINGERPRINT = "Z.fingerprint";
private static final String[] CODENAMES_RELEASED = { /* empty */};
private static final String[] CODENAMES_PRE_RELEASE = {PRE_RELEASE};
@@ -199,13 +199,14 @@ public class PackageParserLegacyCoreTest {
}
private void verifyComputeTargetSdkVersion(int targetSdkVersion, String targetSdkCodename,
- boolean isPlatformReleased, int expectedTargetSdk) {
+ boolean isPlatformReleased, boolean allowUnknownCodenames, int expectedTargetSdk) {
final ParseTypeImpl input = ParseTypeImpl.forParsingWithoutPlatformCompat();
final ParseResult<Integer> result = FrameworkParsingPackageUtils.computeTargetSdkVersion(
targetSdkVersion,
targetSdkCodename,
isPlatformReleased ? CODENAMES_RELEASED : CODENAMES_PRE_RELEASE,
- input);
+ input,
+ allowUnknownCodenames);
if (expectedTargetSdk == -1) {
assertTrue(result.isError());
@@ -220,40 +221,61 @@ public class PackageParserLegacyCoreTest {
// Do allow older release targetSdkVersion on pre-release platform.
// APP: Released API 10
// DEV: Pre-release API 20
- verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, false, OLDER_VERSION);
+ verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, false, false, OLDER_VERSION);
// Do allow same release targetSdkVersion on pre-release platform.
// APP: Released API 20
// DEV: Pre-release API 20
- verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, false, PLATFORM_VERSION);
+ verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, false, false, PLATFORM_VERSION);
// Do allow newer release targetSdkVersion on pre-release platform.
// APP: Released API 30
// DEV: Pre-release API 20
- verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, false, NEWER_VERSION);
+ verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, false, false, NEWER_VERSION);
// Don't allow older pre-release targetSdkVersion on pre-release platform.
// APP: Pre-release API 10
// DEV: Pre-release API 20
- verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, false, -1);
- verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, false, -1);
+ verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, false, false, -1);
+ verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, false,
+ false, -1
+ );
+ // Don't allow older pre-release targetSdkVersion on pre-release platform when
+ // allowUnknownCodenames is true.
+ // APP: Pre-release API 10
+ // DEV: Pre-release API 20
+ verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, false,
+ true, -1);
+ verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, false,
+ true, -1);
// Do allow same pre-release targetSdkVersion on pre-release platform,
// but overwrite the specified version with CUR_DEVELOPMENT.
// APP: Pre-release API 20
// DEV: Pre-release API 20
verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, false,
- Build.VERSION_CODES.CUR_DEVELOPMENT);
+ false, Build.VERSION_CODES.CUR_DEVELOPMENT);
verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, false,
- Build.VERSION_CODES.CUR_DEVELOPMENT);
-
+ false, Build.VERSION_CODES.CUR_DEVELOPMENT);
// Don't allow newer pre-release targetSdkVersion on pre-release platform.
// APP: Pre-release API 30
// DEV: Pre-release API 20
- verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false, -1);
- verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, false, -1);
+ verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false, false, -1);
+ verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, false,
+ false, -1
+ );
+
+ // Do allow newer pre-release targetSdkVersion on pre-release platform when
+ // allowUnknownCodenames is true.
+ // APP: Pre-release API 30
+ // DEV: Pre-release API 20
+ verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false,
+ true, Build.VERSION_CODES.CUR_DEVELOPMENT);
+ verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, false,
+ true, Build.VERSION_CODES.CUR_DEVELOPMENT);
+
}
@Test
@@ -261,36 +283,58 @@ public class PackageParserLegacyCoreTest {
// Do allow older release targetSdkVersion on released platform.
// APP: Released API 10
// DEV: Released API 20
- verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, true, OLDER_VERSION);
+ verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, true, false, OLDER_VERSION);
// Do allow same release targetSdkVersion on released platform.
// APP: Released API 20
// DEV: Released API 20
- verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, true, PLATFORM_VERSION);
+ verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, true, false, PLATFORM_VERSION);
// Do allow newer release targetSdkVersion on released platform.
// APP: Released API 30
// DEV: Released API 20
- verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, true, NEWER_VERSION);
+ verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, true, false, NEWER_VERSION);
// Don't allow older pre-release targetSdkVersion on released platform.
// APP: Pre-release API 10
// DEV: Released API 20
- verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true, -1);
- verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, true, -1);
+ verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true, false, -1);
+ verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, true,
+ false, -1
+ );
// Don't allow same pre-release targetSdkVersion on released platform.
// APP: Pre-release API 20
// DEV: Released API 20
- verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, -1);
- verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, true, -1);
+ verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, false, -1);
+ verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, true, false,
+ -1
+ );
+ // Don't allow same pre-release targetSdkVersion on released platform when
+ // allowUnknownCodenames is true.
+ // APP: Pre-release API 20
+ // DEV: Released API 20
+ verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, true,
+ -1);
+ verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, true, true,
+ -1);
// Don't allow newer pre-release targetSdkVersion on released platform.
// APP: Pre-release API 30
// DEV: Released API 20
- verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, -1);
- verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, true, -1);
+ verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, false, -1);
+ verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, true,
+ false, -1
+ );
+ // Do allow newer pre-release targetSdkVersion on released platform when
+ // allowUnknownCodenames is true.
+ // APP: Pre-release API 30
+ // DEV: Released API 20
+ verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, true,
+ Build.VERSION_CODES.CUR_DEVELOPMENT);
+ verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, true,
+ true, Build.VERSION_CODES.CUR_DEVELOPMENT);
}
/**
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java
index 1d9ea4b6028c..0b144dc7ee3a 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java
@@ -41,6 +41,8 @@ import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class ApexSharedLibraryUpdaterTest extends PackageSharedLibraryUpdaterTest {
+ private static final String SDK_INT_PLUS_ONE = "" + (Build.VERSION.SDK_INT + 1);
+ private static final String SDK_INT_PLUS_TWO = "" + (Build.VERSION.SDK_INT + 2);
private final ArrayMap<String, SystemConfig.SharedLibraryEntry> mSharedLibraries =
new ArrayMap<>(8);
@@ -51,14 +53,19 @@ public class ApexSharedLibraryUpdaterTest extends PackageSharedLibraryUpdaterTes
private void installSharedLibraries() throws Exception {
mSharedLibraries.clear();
- insertLibrary("foo", 0, 0);
- insertLibrary("fooBcpSince30", 30, 0);
- insertLibrary("fooBcpBefore30", 0, 30);
- insertLibrary("fooFromFuture", Build.VERSION.SDK_INT + 2, 0);
+ insertLibrary("foo", null, null);
+ insertLibrary("fooBcpSince30", "30", null);
+ insertLibrary("fooBcpBefore30", null, "30");
+ // simulate libraries being added to the BCP in a future release
+ insertLibrary("fooSinceFuture", SDK_INT_PLUS_ONE, null);
+ insertLibrary("fooSinceFutureCodename", "Z", null);
+ // simulate libraries being removed from the BCP in a future release
+ insertLibrary("fooBcpBeforeFuture", null, SDK_INT_PLUS_ONE);
+ insertLibrary("fooBcpBeforeFutureCodename", null, "Z");
}
- private void insertLibrary(String libraryName, int onBootclasspathSince,
- int onBootclasspathBefore) {
+ private void insertLibrary(String libraryName, String onBootclasspathSince,
+ String onBootclasspathBefore) {
mSharedLibraries.put(libraryName, new SystemConfig.SharedLibraryEntry(
libraryName,
"foo.jar",
@@ -112,7 +119,7 @@ public class ApexSharedLibraryUpdaterTest extends PackageSharedLibraryUpdaterTes
}
@Test
- public void testBcpSince11kNotAppliedWithoutLibrary() {
+ public void testBcpSinceFutureNotAppliedWithoutLibrary() {
ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
.setTargetSdkVersion(Build.VERSION_CODES.R)
.hideAsParsed());
@@ -128,15 +135,17 @@ public class ApexSharedLibraryUpdaterTest extends PackageSharedLibraryUpdaterTes
}
@Test
- public void testBcpSince11kNotAppliedWithLibrary() {
+ public void testBcpSinceFutureNotAppliedWithLibrary() {
ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
.setTargetSdkVersion(Build.VERSION_CODES.R)
- .addUsesLibrary("fooFromFuture")
+ .addUsesLibrary("fooSinceFuture")
+ .addUsesLibrary("fooSinceFutureCodename")
.hideAsParsed());
AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
.setTargetSdkVersion(Build.VERSION_CODES.R)
- .addUsesLibrary("fooFromFuture")
+ .addUsesLibrary("fooSinceFuture")
+ .addUsesLibrary("fooSinceFutureCodename")
.hideAsParsed())
.hideAsFinal();
@@ -183,7 +192,7 @@ public class ApexSharedLibraryUpdaterTest extends PackageSharedLibraryUpdaterTes
*/
@Test
public void testBcpRemovedThenAddedPast() {
- insertLibrary("fooBcpRemovedThenAdded", 30, 28);
+ insertLibrary("fooBcpRemovedThenAdded", "30", "28");
ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
.setTargetSdkVersion(Build.VERSION_CODES.N)
@@ -207,7 +216,8 @@ public class ApexSharedLibraryUpdaterTest extends PackageSharedLibraryUpdaterTes
*/
@Test
public void testBcpRemovedThenAddedMiddle_targetQ() {
- insertLibrary("fooBcpRemovedThenAdded", Build.VERSION.SDK_INT + 1, 30);
+ insertLibrary("fooBcpRemovedThenAdded", SDK_INT_PLUS_ONE, "30");
+ insertLibrary("fooBcpRemovedThenAddedCodename", "Z", "30");
ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
.setTargetSdkVersion(Build.VERSION_CODES.Q)
@@ -217,6 +227,7 @@ public class ApexSharedLibraryUpdaterTest extends PackageSharedLibraryUpdaterTes
.setTargetSdkVersion(Build.VERSION_CODES.Q)
.addUsesLibrary("fooBcpRemovedThenAdded")
.addUsesLibrary("fooBcpBefore30")
+ .addUsesLibrary("fooBcpRemovedThenAddedCodename")
.hideAsParsed())
.hideAsFinal();
@@ -232,7 +243,8 @@ public class ApexSharedLibraryUpdaterTest extends PackageSharedLibraryUpdaterTes
*/
@Test
public void testBcpRemovedThenAddedMiddle_targetR() {
- insertLibrary("fooBcpRemovedThenAdded", Build.VERSION.SDK_INT + 1, 30);
+ insertLibrary("fooBcpRemovedThenAdded", SDK_INT_PLUS_ONE, "30");
+ insertLibrary("fooBcpRemovedThenAddedCodename", "Z", "30");
ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
.setTargetSdkVersion(Build.VERSION_CODES.R)
@@ -256,7 +268,8 @@ public class ApexSharedLibraryUpdaterTest extends PackageSharedLibraryUpdaterTes
*/
@Test
public void testBcpRemovedThenAddedMiddle_targetR_usingLib() {
- insertLibrary("fooBcpRemovedThenAdded", Build.VERSION.SDK_INT + 1, 30);
+ insertLibrary("fooBcpRemovedThenAdded", SDK_INT_PLUS_ONE, "30");
+ insertLibrary("fooBcpRemovedThenAddedCodename", "Z", "30");
ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
.setTargetSdkVersion(Build.VERSION_CODES.R)
@@ -274,6 +287,82 @@ public class ApexSharedLibraryUpdaterTest extends PackageSharedLibraryUpdaterTes
checkBackwardsCompatibility(before, after);
}
+ /**
+ * Test a library that was first removed from the BCP [to a mainline module] and later was
+ * moved back to the BCP via a mainline module update. Both things happening in future SDKs.
+ */
+ @Test
+ public void testBcpRemovedThenAddedFuture() {
+ insertLibrary("fooBcpRemovedThenAdded", SDK_INT_PLUS_TWO, SDK_INT_PLUS_ONE);
+ ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+ .setTargetSdkVersion(Build.VERSION_CODES.R)
+ .hideAsParsed());
+
+ AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+ .setTargetSdkVersion(Build.VERSION_CODES.R)
+ .hideAsParsed())
+ .hideAsFinal();
+
+ // in this example, we are at the point where the library is still in the BCP
+ checkBackwardsCompatibility(before, after);
+ }
+
+ /**
+ * Test a library that was first removed from the BCP [to a mainline module] and later was
+ * moved back to the BCP via a mainline module update. Both things happening in future SDKs.
+ */
+ @Test
+ public void testBcpRemovedThenAddedFuture_usingLib() {
+ insertLibrary("fooBcpRemovedThenAdded", SDK_INT_PLUS_TWO, SDK_INT_PLUS_ONE);
+
+ ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+ .setTargetSdkVersion(Integer.parseInt(SDK_INT_PLUS_ONE))
+ .addUsesLibrary("fooBcpRemovedThenAdded")
+ .hideAsParsed());
+
+ AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+ .setTargetSdkVersion(Integer.parseInt(SDK_INT_PLUS_ONE))
+ .hideAsParsed())
+ .hideAsFinal();
+
+ // in this example, we are at the point where the library was removed from the BCP
+ checkBackwardsCompatibility(before, after);
+ }
+
+ @Test
+ public void testBcpBeforeFuture() {
+ ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+ .setTargetSdkVersion(Build.VERSION_CODES.R)
+ .addUsesLibrary("fooBcpBeforeFuture")
+ .addUsesLibrary("fooBcpBeforeFutureCodename")
+ .hideAsParsed());
+
+ AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+ .setTargetSdkVersion(Build.VERSION_CODES.R)
+ .hideAsParsed())
+ .hideAsFinal();
+
+ // in this example, we are at the point where the library was removed from the BCP
+ checkBackwardsCompatibility(before, after);
+ }
+
+ @Test
+ public void testBcpBeforeFuture_futureTargetSdk() {
+ ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+ .setTargetSdkVersion(Integer.parseInt(SDK_INT_PLUS_ONE))
+ .addUsesLibrary("fooBcpBeforeFuture")
+ .addUsesLibrary("fooBcpBeforeFutureCodename")
+ .hideAsParsed());
+
+ AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+ .setTargetSdkVersion(Integer.parseInt(SDK_INT_PLUS_ONE))
+ .hideAsParsed())
+ .hideAsFinal();
+
+ // in this example, we are at the point where the library was removed from the BCP
+ checkBackwardsCompatibility(before, after);
+ }
+
private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after) {
checkBackwardsCompatibility(before, after,
() -> new ApexSharedLibraryUpdater(mSharedLibraries));
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java
index 6bdd88c6a712..2d0755d00ba8 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundHw2CompatTest.java
@@ -20,18 +20,15 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
@@ -56,32 +53,20 @@ import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.mockito.ArgumentCaptor;
-import java.util.LinkedList;
-import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
@RunWith(Parameterized.class)
public class SoundHw2CompatTest {
- @Parameterized.Parameter(0) public String mVersion;
- @Parameterized.Parameter(1) public boolean mSupportConcurrentCapture;
+ @Parameterized.Parameter public String mVersion;
private final Runnable mRebootRunnable = mock(Runnable.class);
private ISoundTriggerHal mCanonical;
- private CaptureStateNotifier mCaptureStateNotifier;
private android.hardware.soundtrigger.V2_0.ISoundTriggerHw mHalDriver;
// We run the test once for every version of the underlying driver.
- @Parameterized.Parameters(name = "{0}, concurrent={1}")
- public static Iterable<Object[]> data() {
- List<Object[]> result = new LinkedList<>();
-
- for (String version : new String[]{"V2_0", "V2_1", "V2_2", "V2_3",}) {
- for (boolean concurrentCapture : new boolean[]{false, true}) {
- result.add(new Object[]{version, concurrentCapture});
- }
- }
-
- return result;
+ @Parameterized.Parameters
+ public static Object[] data() {
+ return new String[]{"V2_0", "V2_1", "V2_2", "V2_3"};
}
@Before
@@ -139,7 +124,7 @@ public class SoundHw2CompatTest {
when(mHalDriver.asBinder()).thenReturn(binder);
android.hardware.soundtrigger.V2_3.Properties halProperties =
- TestUtil.createDefaultProperties_2_3(mSupportConcurrentCapture);
+ TestUtil.createDefaultProperties_2_3();
doAnswer(invocation -> {
((android.hardware.soundtrigger.V2_0.ISoundTriggerHw.getPropertiesCallback) invocation.getArgument(
0)).onValues(0, halProperties.base);
@@ -156,10 +141,7 @@ public class SoundHw2CompatTest {
}).when(driver).getProperties_2_3(any());
}
- mCaptureStateNotifier = spy(new CaptureStateNotifier());
-
- mCanonical = SoundTriggerHw2Compat.create(mHalDriver, mRebootRunnable,
- mCaptureStateNotifier);
+ mCanonical = SoundTriggerHw2Compat.create(mHalDriver, mRebootRunnable, null);
// During initialization any method can be called, but after we're starting to enforce that
// no additional methods are called.
@@ -171,7 +153,6 @@ public class SoundHw2CompatTest {
mCanonical.detach();
verifyNoMoreInteractions(mHalDriver);
verifyNoMoreInteractions(mRebootRunnable);
- mCaptureStateNotifier.verifyNoMoreListeners();
}
@Test
@@ -194,12 +175,12 @@ public class SoundHw2CompatTest {
// It is OK for the SUT to cache the properties, so the underlying method doesn't
// need to be called every single time.
verify(driver, atMost(1)).getProperties_2_3(any());
- TestUtil.validateDefaultProperties(properties, mSupportConcurrentCapture);
+ TestUtil.validateDefaultProperties(properties);
} else {
// It is OK for the SUT to cache the properties, so the underlying method doesn't
// need to be called every single time.
verify(mHalDriver, atMost(1)).getProperties(any());
- TestUtil.validateDefaultProperties(properties, mSupportConcurrentCapture, 0, "");
+ TestUtil.validateDefaultProperties(properties, 0, "");
}
}
@@ -291,7 +272,7 @@ public class SoundHw2CompatTest {
ISoundTriggerHal.ModelCallback canonicalCallback = mock(
ISoundTriggerHal.ModelCallback.class);
- final int maxModels = TestUtil.createDefaultProperties_2_0(false).maxSoundModels;
+ final int maxModels = TestUtil.createDefaultProperties_2_0().maxSoundModels;
int[] modelHandles = new int[maxModels];
// Load as many models as we're allowed.
@@ -318,7 +299,7 @@ public class SoundHw2CompatTest {
verify(globalCallback).onResourcesAvailable();
}
- private int loadPhraseModel_2_0(ISoundTriggerHal.ModelCallback canonicalCallback)
+ private void loadPhraseModel_2_0(ISoundTriggerHal.ModelCallback canonicalCallback)
throws Exception {
final int handle = 29;
ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel>
@@ -345,10 +326,9 @@ public class SoundHw2CompatTest {
TestUtil.validatePhraseSoundModel_2_0(modelCaptor.getValue());
validateCallback_2_0(callbackCaptor.getValue(), canonicalCallback);
- return handle;
}
- private int loadPhraseModel_2_1(ISoundTriggerHal.ModelCallback canonicalCallback)
+ private void loadPhraseModel_2_1(ISoundTriggerHal.ModelCallback canonicalCallback)
throws Exception {
final android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver_2_1 =
(android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
@@ -380,14 +360,13 @@ public class SoundHw2CompatTest {
TestUtil.validatePhraseSoundModel_2_1(model.get());
validateCallback_2_1(callbackCaptor.getValue(), canonicalCallback);
- return handle;
}
- public int loadPhraseModel(ISoundTriggerHal.ModelCallback canonicalCallback) throws Exception {
+ public void loadPhraseModel(ISoundTriggerHal.ModelCallback canonicalCallback) throws Exception {
if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
- return loadPhraseModel_2_1(canonicalCallback);
+ loadPhraseModel_2_1(canonicalCallback);
} else {
- return loadPhraseModel_2_0(canonicalCallback);
+ loadPhraseModel_2_0(canonicalCallback);
}
}
@@ -484,80 +463,6 @@ public class SoundHw2CompatTest {
}
@Test
- public void testConcurrentCaptureAbort() throws Exception {
- assumeFalse(mSupportConcurrentCapture);
- verify(mCaptureStateNotifier, atLeast(1)).registerListener(any());
-
- // Register global callback.
- ISoundTriggerHal.GlobalCallback globalCallback = mock(
- ISoundTriggerHal.GlobalCallback.class);
- mCanonical.registerCallback(globalCallback);
-
- // Load.
- ISoundTriggerHal.ModelCallback canonicalCallback = mock(
- ISoundTriggerHal.ModelCallback.class);
- final int handle = loadGenericModel(canonicalCallback);
-
- // Then start.
- startRecognition(handle, canonicalCallback);
-
- // Now activate external capture.
- mCaptureStateNotifier.setState(true);
-
- // Expect hardware to have been stopped.
- verify(mHalDriver).stopRecognition(handle);
-
- // Expect an abort event (async).
- ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
- RecognitionEvent.class);
- mCanonical.flushCallbacks();
- verify(canonicalCallback).recognitionCallback(eq(handle), eventCaptor.capture());
- assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().status);
-
- // Deactivate external capture.
- mCaptureStateNotifier.setState(false);
-
- // Expect a onResourcesAvailable().
- mCanonical.flushCallbacks();
- verify(globalCallback).onResourcesAvailable();
- }
-
- @Test
- public void testConcurrentCaptureReject() throws Exception {
- assumeFalse(mSupportConcurrentCapture);
- verify(mCaptureStateNotifier, atLeast(1)).registerListener(any());
-
- // Register global callback.
- ISoundTriggerHal.GlobalCallback globalCallback = mock(
- ISoundTriggerHal.GlobalCallback.class);
- mCanonical.registerCallback(globalCallback);
-
- // Load (this registers the callback).
- ISoundTriggerHal.ModelCallback canonicalCallback = mock(
- ISoundTriggerHal.ModelCallback.class);
- final int handle = loadGenericModel(canonicalCallback);
-
- // Report external capture active.
- mCaptureStateNotifier.setState(true);
-
- // Then start.
- RecognitionConfig config = TestUtil.createRecognitionConfig();
- try {
- mCanonical.startRecognition(handle, 203, 204, config);
- fail("Expected an exception");
- } catch (RecoverableException e) {
- assertEquals(Status.RESOURCE_CONTENTION, e.errorCode);
- }
-
- // Deactivate external capture.
- mCaptureStateNotifier.setState(false);
-
- // Expect a onResourcesAvailable().
- mCanonical.flushCallbacks();
- verify(globalCallback).onResourcesAvailable();
- }
-
- @Test
public void testStopRecognition() throws Exception {
mCanonical.stopRecognition(17);
verify(mHalDriver).stopRecognition(17);
@@ -675,7 +580,7 @@ public class SoundHw2CompatTest {
}
@Test
- public void testGlobalCallback() throws Exception {
+ public void testGlobalCallback() {
testGlobalCallback_2_0();
}
@@ -803,29 +708,4 @@ public class SoundHw2CompatTest {
verifyNoMoreInteractions(canonicalCallback);
clearInvocations(canonicalCallback);
}
-
- public static class CaptureStateNotifier implements ICaptureStateNotifier {
- private final List<Listener> mListeners = new LinkedList<>();
-
- @Override
- public boolean registerListener(Listener listener) {
- mListeners.add(listener);
- return false;
- }
-
- @Override
- public void unregisterListener(Listener listener) {
- mListeners.remove(listener);
- }
-
- public void setState(boolean state) {
- for (Listener listener : mListeners) {
- listener.onCaptureStateChange(state);
- }
- }
-
- public void verifyNoMoreListeners() {
- assertEquals(0, mListeners.size());
- }
- }
}
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java
new file mode 100644
index 000000000000..61989252d04d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerHalConcurrentCaptureHandlerTest.java
@@ -0,0 +1,313 @@
+/*
+ * 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.server.soundtrigger_middleware;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atMost;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+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.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.media.soundtrigger.RecognitionEvent;
+import android.media.soundtrigger.RecognitionStatus;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+
+@RunWith(JUnit4.class)
+public class SoundTriggerHalConcurrentCaptureHandlerTest {
+ private ISoundTriggerHal mUnderlying;
+ private CaptureStateNotifier mNotifier;
+ private ISoundTriggerHal.GlobalCallback mGlobalCallback;
+ private SoundTriggerHalConcurrentCaptureHandler mHandler;
+
+ @Before
+ public void setUp() {
+ mNotifier = new CaptureStateNotifier();
+ mUnderlying = mock(ISoundTriggerHal.class);
+ mGlobalCallback = mock(ISoundTriggerHal.GlobalCallback.class);
+ mHandler = new SoundTriggerHalConcurrentCaptureHandler(mUnderlying, mNotifier);
+ mHandler.registerCallback(mGlobalCallback);
+ }
+
+ @Test
+ public void testBasic() throws Exception {
+ ISoundTriggerHal.ModelCallback callback = mock(ISoundTriggerHal.ModelCallback.class);
+ int handle = mHandler.loadSoundModel(TestUtil.createGenericSoundModel(), callback);
+ verify(mUnderlying).loadSoundModel(any(), any());
+
+ mHandler.startRecognition(handle, 101, 102, TestUtil.createRecognitionConfig());
+ verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
+
+ mNotifier.setActive(true);
+ verify(mUnderlying).stopRecognition(handle);
+ ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEvent.class);
+ Thread.sleep(50);
+ verify(callback).recognitionCallback(eq(handle), eventCaptor.capture());
+ RecognitionEvent event = eventCaptor.getValue();
+ assertEquals(event.status, RecognitionStatus.ABORTED);
+ assertFalse(event.recognitionStillActive);
+ verifyZeroInteractions(mGlobalCallback);
+ clearInvocations(callback, mUnderlying);
+
+ mNotifier.setActive(false);
+ Thread.sleep(50);
+ verify(mGlobalCallback).onResourcesAvailable();
+ verifyNoMoreInteractions(callback, mUnderlying);
+
+ mNotifier.setActive(true);
+ verifyNoMoreInteractions(callback, mUnderlying);
+ }
+
+ @Test
+ public void testStopBeforeActive() throws Exception {
+ ISoundTriggerHal.ModelCallback callback = mock(ISoundTriggerHal.ModelCallback.class);
+ int handle = mHandler.loadSoundModel(TestUtil.createGenericSoundModel(), callback);
+ verify(mUnderlying).loadSoundModel(any(), any());
+
+ mHandler.startRecognition(handle, 101, 102, TestUtil.createRecognitionConfig());
+ verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
+ mHandler.stopRecognition(handle);
+ verify(mUnderlying).stopRecognition(handle);
+ clearInvocations(mUnderlying);
+
+ mNotifier.setActive(true);
+ Thread.sleep(50);
+ verifyNoMoreInteractions(mUnderlying);
+ verifyNoMoreInteractions(callback);
+ }
+
+ @Test
+ public void testStopAfterActive() {
+ ISoundTriggerHal.ModelCallback callback = mock(ISoundTriggerHal.ModelCallback.class);
+ int handle = mHandler.loadSoundModel(TestUtil.createGenericSoundModel(), callback);
+ verify(mUnderlying).loadSoundModel(any(), any());
+
+ mHandler.startRecognition(handle, 101, 102, TestUtil.createRecognitionConfig());
+ verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
+
+ mNotifier.setActive(true);
+ verify(mUnderlying, times(1)).stopRecognition(handle);
+ mHandler.stopRecognition(handle);
+ verify(callback, times(1)).recognitionCallback(eq(handle), any());
+ }
+
+ @Test(timeout = 200)
+ public void testAbortWhileStop() {
+ ISoundTriggerHal.ModelCallback callback = mock(ISoundTriggerHal.ModelCallback.class);
+ int handle = mHandler.loadSoundModel(TestUtil.createGenericSoundModel(), callback);
+ ArgumentCaptor<ISoundTriggerHal.ModelCallback> modelCallbackCaptor =
+ ArgumentCaptor.forClass(ISoundTriggerHal.ModelCallback.class);
+ verify(mUnderlying).loadSoundModel(any(), modelCallbackCaptor.capture());
+ ISoundTriggerHal.ModelCallback modelCallback = modelCallbackCaptor.getValue();
+
+ mHandler.startRecognition(handle, 101, 102, TestUtil.createRecognitionConfig());
+ verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
+
+ doAnswer(invocation -> {
+ RecognitionEvent event = TestUtil.createRecognitionEvent(RecognitionStatus.ABORTED,
+ false);
+ // Call the callback from a different thread to detect deadlocks by preventing recursive
+ // locking from working.
+ runOnSeparateThread(() -> modelCallback.recognitionCallback(handle, event));
+ return null;
+ }).when(mUnderlying).stopRecognition(handle);
+ mHandler.stopRecognition(handle);
+ verify(mUnderlying, times(1)).stopRecognition(handle);
+
+ ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEvent.class);
+ verify(callback, atMost(1)).recognitionCallback(eq(handle), eventCaptor.capture());
+ }
+
+ @Test(timeout = 200)
+ public void testActiveWhileStop() {
+ ISoundTriggerHal.ModelCallback callback = mock(ISoundTriggerHal.ModelCallback.class);
+ int handle = mHandler.loadSoundModel(TestUtil.createGenericSoundModel(), callback);
+ ArgumentCaptor<ISoundTriggerHal.ModelCallback> modelCallbackCaptor =
+ ArgumentCaptor.forClass(ISoundTriggerHal.ModelCallback.class);
+ verify(mUnderlying).loadSoundModel(any(), modelCallbackCaptor.capture());
+ ISoundTriggerHal.ModelCallback modelCallback = modelCallbackCaptor.getValue();
+
+ mHandler.startRecognition(handle, 101, 102, TestUtil.createRecognitionConfig());
+ verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
+
+ doAnswer(invocation -> {
+ // The stop request causes a callback to be flushed.
+ RecognitionEvent event = TestUtil.createRecognitionEvent(RecognitionStatus.FORCED,
+ true);
+ // Call the callback from a different thread to detect deadlocks by preventing recursive
+ // locking from working.
+ runOnSeparateThread(() -> modelCallback.recognitionCallback(handle, event));
+ // While the HAL is processing the stop request, capture state becomes active.
+ new Thread(() -> mNotifier.setActive(true)).start();
+ Thread.sleep(50);
+ return null;
+ }).when(mUnderlying).stopRecognition(handle);
+ mHandler.stopRecognition(handle);
+ // We only expect one underlying invocation of stop().
+ verify(mUnderlying, times(1)).stopRecognition(handle);
+
+ // The callback shouldn't be invoked in this case.
+ verify(callback, never()).recognitionCallback(eq(handle), any());
+ }
+
+ @Test(timeout = 200)
+ public void testStopWhileActive() {
+ ISoundTriggerHal.ModelCallback callback = mock(ISoundTriggerHal.ModelCallback.class);
+ int handle = mHandler.loadSoundModel(TestUtil.createGenericSoundModel(), callback);
+ ArgumentCaptor<ISoundTriggerHal.ModelCallback> modelCallbackCaptor =
+ ArgumentCaptor.forClass(ISoundTriggerHal.ModelCallback.class);
+ verify(mUnderlying).loadSoundModel(any(), modelCallbackCaptor.capture());
+ ISoundTriggerHal.ModelCallback modelCallback = modelCallbackCaptor.getValue();
+
+ mHandler.startRecognition(handle, 101, 102, TestUtil.createRecognitionConfig());
+ verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
+
+ doAnswer(invocation -> {
+ // The stop request causes a callback to be flushed.
+ RecognitionEvent event = TestUtil.createRecognitionEvent(RecognitionStatus.FORCED,
+ true);
+ // Call the callback from a different thread to detect deadlocks by preventing recursive
+ // locking from working.
+ runOnSeparateThread(() -> modelCallback.recognitionCallback(handle, event));
+ // While the HAL is processing the stop request, client requests stop.
+ new Thread(() -> mHandler.stopRecognition(handle)).start();
+ Thread.sleep(50);
+ return null;
+ }).when(mUnderlying).stopRecognition(handle);
+ mNotifier.setActive(true);
+ // We only expect one underlying invocation of stop().
+ verify(mUnderlying, times(1)).stopRecognition(handle);
+ verify(callback, atMost(1)).recognitionCallback(eq(handle), any());
+ }
+
+ @Test(timeout = 200)
+ public void testEventWhileActive() throws Exception {
+ ISoundTriggerHal.ModelCallback callback = mock(ISoundTriggerHal.ModelCallback.class);
+ int handle = mHandler.loadSoundModel(TestUtil.createGenericSoundModel(), callback);
+ ArgumentCaptor<ISoundTriggerHal.ModelCallback> modelCallbackCaptor =
+ ArgumentCaptor.forClass(ISoundTriggerHal.ModelCallback.class);
+ verify(mUnderlying).loadSoundModel(any(), modelCallbackCaptor.capture());
+ ISoundTriggerHal.ModelCallback modelCallback = modelCallbackCaptor.getValue();
+
+ mHandler.startRecognition(handle, 101, 102, TestUtil.createRecognitionConfig());
+ verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
+
+ doAnswer(invocation -> {
+ RecognitionEvent event = TestUtil.createRecognitionEvent(RecognitionStatus.SUCCESS,
+ false);
+ // Call the callback from a different thread to detect deadlocks by preventing recursive
+ // locking from working.
+ runOnSeparateThread(() -> modelCallback.recognitionCallback(handle, event));
+ return null;
+ }).when(mUnderlying).stopRecognition(handle);
+ mNotifier.setActive(true);
+ verify(mUnderlying, times(1)).stopRecognition(handle);
+ Thread.sleep(50);
+
+ ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEvent.class);
+ verify(callback, atMost(2)).recognitionCallback(eq(handle), eventCaptor.capture());
+ RecognitionEvent lastEvent = eventCaptor.getValue();
+ assertEquals(lastEvent.status, RecognitionStatus.ABORTED);
+ assertFalse(lastEvent.recognitionStillActive);
+ }
+
+
+ @Test(timeout = 200)
+ public void testNonFinalEventWhileActive() throws Exception {
+ ISoundTriggerHal.ModelCallback callback = mock(ISoundTriggerHal.ModelCallback.class);
+ int handle = mHandler.loadSoundModel(TestUtil.createGenericSoundModel(), callback);
+ ArgumentCaptor<ISoundTriggerHal.ModelCallback> modelCallbackCaptor =
+ ArgumentCaptor.forClass(ISoundTriggerHal.ModelCallback.class);
+ verify(mUnderlying).loadSoundModel(any(), modelCallbackCaptor.capture());
+ ISoundTriggerHal.ModelCallback modelCallback = modelCallbackCaptor.getValue();
+
+ mHandler.startRecognition(handle, 101, 102, TestUtil.createRecognitionConfig());
+ verify(mUnderlying).startRecognition(eq(handle), eq(101), eq(102), any());
+
+ doAnswer(invocation -> {
+ RecognitionEvent event = TestUtil.createRecognitionEvent(RecognitionStatus.FORCED,
+ true);
+ // Call the callback from a different thread to detect deadlocks by preventing recursive
+ // locking from working.
+ runOnSeparateThread(() -> modelCallback.recognitionCallback(handle, event));
+
+ return null;
+ }).when(mUnderlying).stopRecognition(handle);
+ mNotifier.setActive(true);
+ verify(mUnderlying, times(1)).stopRecognition(handle);
+
+ Thread.sleep(50);
+ ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEvent.class);
+ verify(callback, atMost(2)).recognitionCallback(eq(handle), eventCaptor.capture());
+ RecognitionEvent lastEvent = eventCaptor.getValue();
+ assertEquals(lastEvent.status, RecognitionStatus.ABORTED);
+ assertFalse(lastEvent.recognitionStillActive);
+ }
+
+ private static void runOnSeparateThread(Runnable runnable) {
+ Thread thread = new Thread(runnable);
+ thread.start();
+ try {
+ thread.join();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static class CaptureStateNotifier implements ICaptureStateNotifier {
+ boolean mActive = false;
+ Listener mListener;
+
+ @Override
+ public boolean registerListener(@NonNull Listener listener) {
+ mListener = listener;
+ return mActive;
+ }
+
+ @Override
+ public void unregisterListener(@NonNull Listener listener) {
+ mListener = null;
+ }
+
+ public void setActive(boolean active) {
+ mActive = active;
+ if (mListener != null) {
+ // Call the callback from a different thread to detect deadlocks by preventing
+ // recursive locking from working.
+ runOnSeparateThread(() -> mListener.onCaptureStateChange(mActive));
+ }
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
index 0187e34cbc5f..3bebc94fe0ed 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
@@ -134,7 +134,7 @@ public class SoundTriggerMiddlewareImplTest {
public void setUp() throws Exception {
clearInvocations(mHalDriver);
clearInvocations(mAudioSessionProvider);
- when(mHalDriver.getProperties()).thenReturn(TestUtil.createDefaultProperties(false));
+ when(mHalDriver.getProperties()).thenReturn(TestUtil.createDefaultProperties());
mService = new SoundTriggerMiddlewareImpl(() -> mHalDriver, mAudioSessionProvider);
}
@@ -156,7 +156,7 @@ public class SoundTriggerMiddlewareImplTest {
assertEquals(1, allDescriptors.length);
Properties properties = allDescriptors[0].properties;
- assertEquals(TestUtil.createDefaultProperties(false), properties);
+ assertEquals(TestUtil.createDefaultProperties(), properties);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/TestUtil.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/TestUtil.java
index 30b4a59b32b1..39561f74d7ed 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/TestUtil.java
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/TestUtil.java
@@ -162,8 +162,8 @@ class TestUtil {
phrases.get(0).recognitionModes);
}
- static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties createDefaultProperties_2_0(
- boolean supportConcurrentCapture) {
+ static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties
+ createDefaultProperties_2_0() {
android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties properties =
new android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties();
properties.implementor = "implementor";
@@ -185,17 +185,16 @@ class TestUtil {
| android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER;
properties.captureTransition = true;
properties.maxBufferMs = 321;
- properties.concurrentCapture = supportConcurrentCapture;
+ properties.concurrentCapture = true;
properties.triggerInEvent = true;
properties.powerConsumptionMw = 432;
return properties;
}
- static android.hardware.soundtrigger.V2_3.Properties createDefaultProperties_2_3(
- boolean supportConcurrentCapture) {
+ static android.hardware.soundtrigger.V2_3.Properties createDefaultProperties_2_3() {
android.hardware.soundtrigger.V2_3.Properties properties =
new android.hardware.soundtrigger.V2_3.Properties();
- properties.base = createDefaultProperties_2_0(supportConcurrentCapture);
+ properties.base = createDefaultProperties_2_0();
properties.supportedModelArch = "supportedModelArch";
properties.audioCapabilities =
android.hardware.soundtrigger.V2_3.AudioCapabilities.ECHO_CANCELLATION
@@ -203,7 +202,7 @@ class TestUtil {
return properties;
}
- static Properties createDefaultProperties(boolean supportConcurrentCapture) {
+ static Properties createDefaultProperties() {
Properties properties = new Properties();
properties.implementor = "implementor";
properties.description = "description";
@@ -217,7 +216,7 @@ class TestUtil {
| RecognitionMode.USER_AUTHENTICATION | RecognitionMode.GENERIC_TRIGGER;
properties.captureTransition = true;
properties.maxBufferMs = 321;
- properties.concurrentCapture = supportConcurrentCapture;
+ properties.concurrentCapture = true;
properties.triggerInEvent = true;
properties.powerConsumptionMw = 432;
properties.supportedModelArch = "supportedModelArch";
@@ -226,13 +225,13 @@ class TestUtil {
return properties;
}
- static void validateDefaultProperties(Properties properties, boolean supportConcurrentCapture) {
- validateDefaultProperties(properties, supportConcurrentCapture,
+ static void validateDefaultProperties(Properties properties) {
+ validateDefaultProperties(properties,
AudioCapabilities.ECHO_CANCELLATION | AudioCapabilities.NOISE_SUPPRESSION,
"supportedModelArch");
}
- static void validateDefaultProperties(Properties properties, boolean supportConcurrentCapture,
+ static void validateDefaultProperties(Properties properties,
@AudioCapabilities int audioCapabilities, @NonNull String supportedModelArch) {
assertEquals("implementor", properties.implementor);
assertEquals("description", properties.description);
@@ -246,7 +245,7 @@ class TestUtil {
properties.recognitionModes);
assertTrue(properties.captureTransition);
assertEquals(321, properties.maxBufferMs);
- assertEquals(supportConcurrentCapture, properties.concurrentCapture);
+ assertEquals(true, properties.concurrentCapture);
assertTrue(properties.triggerInEvent);
assertEquals(432, properties.powerConsumptionMw);
assertEquals(supportedModelArch, properties.supportedModelArch);
diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
index bfdffc0e6567..20486b3e396d 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
@@ -429,18 +429,40 @@ public class SystemConfigTest {
public void readPermissions_allowLibs_parsesSimpleLibrary() throws IOException {
String contents =
"<permissions>\n"
- + " <library \n"
- + " name=\"foo\"\n"
- + " file=\"" + mFooJar + "\"\n"
- + " on-bootclasspath-before=\"10\"\n"
- + " on-bootclasspath-since=\"20\"\n"
- + " />\n\n"
- + " </permissions>";
+ + " <library \n"
+ + " name=\"foo\"\n"
+ + " file=\"" + mFooJar + "\"\n"
+ + " on-bootclasspath-before=\"10\"\n"
+ + " on-bootclasspath-since=\"20\"\n"
+ + " />\n\n"
+ + " </permissions>";
parseSharedLibraries(contents);
assertFooIsOnlySharedLibrary();
SystemConfig.SharedLibraryEntry entry = mSysConfig.getSharedLibraries().get("foo");
- assertThat(entry.onBootclasspathBefore).isEqualTo(10);
- assertThat(entry.onBootclasspathSince).isEqualTo(20);
+ assertThat(entry.onBootclasspathBefore).isEqualTo("10");
+ assertThat(entry.onBootclasspathSince).isEqualTo("20");
+ }
+
+ /**
+ * Tests that readPermissions works correctly for a library with on-bootclasspath-before
+ * and on-bootclasspath-since that uses codenames.
+ */
+ @Test
+ public void readPermissions_allowLibs_parsesSimpleLibraryWithCodenames() throws IOException {
+ String contents =
+ "<permissions>\n"
+ + " <library \n"
+ + " name=\"foo\"\n"
+ + " file=\"" + mFooJar + "\"\n"
+ + " on-bootclasspath-before=\"Q\"\n"
+ + " on-bootclasspath-since=\"W\"\n"
+ + " />\n\n"
+ + " </permissions>";
+ parseSharedLibraries(contents);
+ assertFooIsOnlySharedLibrary();
+ SystemConfig.SharedLibraryEntry entry = mSysConfig.getSharedLibraries().get("foo");
+ assertThat(entry.onBootclasspathBefore).isEqualTo("Q");
+ assertThat(entry.onBootclasspathSince).isEqualTo("W");
}
/**
@@ -461,8 +483,8 @@ public class SystemConfigTest {
parseSharedLibraries(contents);
assertFooIsOnlySharedLibrary();
SystemConfig.SharedLibraryEntry entry = mSysConfig.getSharedLibraries().get("foo");
- assertThat(entry.onBootclasspathBefore).isEqualTo(10);
- assertThat(entry.onBootclasspathSince).isEqualTo(20);
+ assertThat(entry.onBootclasspathBefore).isEqualTo("10");
+ assertThat(entry.onBootclasspathSince).isEqualTo("20");
}
/**
@@ -543,12 +565,20 @@ public class SystemConfigTest {
*/
@Test
public void readPermissions_allowLibs_allowsCurrentMaxSdk() throws IOException {
+ // depending on whether this test is running before or after finalization, we need to
+ // pass a different parameter
+ String parameter;
+ if ("REL".equals(Build.VERSION.CODENAME)) {
+ parameter = "" + Build.VERSION.SDK_INT;
+ } else {
+ parameter = "ZZZ";
+ }
String contents =
"<permissions>\n"
+ " <library \n"
+ " name=\"foo\"\n"
+ " file=\"" + mFooJar + "\"\n"
- + " max-device-sdk=\"" + Build.VERSION.SDK_INT + "\"\n"
+ + " max-device-sdk=\"" + parameter + "\"\n"
+ " />\n\n"
+ " </permissions>";
parseSharedLibraries(contents);
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index f3d494d160f2..4fbf0065f78d 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -602,10 +602,15 @@ public class VibratorManagerServiceTest {
VibrationEffect.EFFECT_HEAVY_CLICK, VibrationEffect.EFFECT_DOUBLE_CLICK);
VibratorManagerService service = createSystemReadyService();
mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+
+ // The haptic feedback should be ignored in low power, but not the ringtone. The end
+ // of the test asserts which actual effects ended up playing.
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), HAPTIC_FEEDBACK_ATTRS);
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 1,
service, TEST_TIMEOUT_MILLIS));
+ // Allow the ringtone to complete, as the other vibrations won't cancel it.
+ assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK),
@@ -815,6 +820,29 @@ public class VibratorManagerServiceTest {
}
@Test
+ public void vibrate_withOngoingRingtoneVibration_ignoresEffect() throws Exception {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ VibratorManagerService service = createSystemReadyService();
+
+ VibrationEffect alarmEffect = VibrationEffect.createWaveform(
+ new long[]{10_000, 10_000}, new int[]{128, 255}, -1);
+ vibrate(service, alarmEffect, new VibrationAttributes.Builder().setUsage(
+ VibrationAttributes.USAGE_RINGTONE).build());
+
+ // VibrationThread will start this vibration async, so wait before checking it started.
+ assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
+ service, TEST_TIMEOUT_MILLIS));
+
+ vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+ HAPTIC_FEEDBACK_ATTRS);
+
+ // Wait before checking it never played a second effect.
+ assertFalse(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 1,
+ service, /* timeout= */ 50));
+ }
+
+ @Test
public void vibrate_withInputDevices_vibratesInputDevices() throws Exception {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index c86f38d4264d..8d4a0176e3be 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -119,13 +119,6 @@ final class HotwordDetectionConnection {
private static final Duration MAX_UPDATE_TIMEOUT_DURATION =
Duration.ofMillis(MAX_UPDATE_TIMEOUT_MILLIS);
private static final long RESET_DEBUG_HOTWORD_LOGGING_TIMEOUT_MILLIS = 60 * 60 * 1000; // 1 hour
- /**
- * Time after which each HotwordDetectionService process is stopped and replaced by a new one.
- * 0 indicates no restarts.
- */
- private static final int RESTART_PERIOD_SECONDS =
- DeviceConfig.getInt(DeviceConfig.NAMESPACE_VOICE_INTERACTION,
- KEY_RESTART_PERIOD_IN_SECONDS, 0);
private static final int MAX_ISOLATED_PROCESS_NUMBER = 10;
// Hotword metrics
@@ -150,6 +143,11 @@ final class HotwordDetectionConnection {
private final @NonNull ServiceConnectionFactory mServiceConnectionFactory;
private final IHotwordRecognitionStatusCallback mCallback;
private final int mDetectorType;
+ /**
+ * Time after which each HotwordDetectionService process is stopped and replaced by a new one.
+ * 0 indicates no restarts.
+ */
+ private final int mReStartPeriodSeconds;
final Object mLock;
final int mVoiceInteractionServiceUid;
@@ -195,6 +193,8 @@ final class HotwordDetectionConnection {
mUser = userId;
mCallback = callback;
mDetectorType = detectorType;
+ mReStartPeriodSeconds = DeviceConfig.getInt(DeviceConfig.NAMESPACE_VOICE_INTERACTION,
+ KEY_RESTART_PERIOD_IN_SECONDS, 0);
final Intent intent = new Intent(HotwordDetectionService.SERVICE_INTERFACE);
intent.setComponent(mDetectionComponentName);
initAudioFlingerLocked();
@@ -206,11 +206,11 @@ final class HotwordDetectionConnection {
mLastRestartInstant = Instant.now();
updateStateAfterProcessStart(options, sharedMemory);
- if (RESTART_PERIOD_SECONDS <= 0) {
+ if (mReStartPeriodSeconds <= 0) {
mCancellationTaskFuture = null;
} else {
- // TODO(volnov): we need to be smarter here, e.g. schedule it a bit more often, but wait
- // until the current session is closed.
+ // TODO: we need to be smarter here, e.g. schedule it a bit more often,
+ // but wait until the current session is closed.
mCancellationTaskFuture = mScheduledExecutorService.scheduleAtFixedRate(() -> {
Slog.v(TAG, "Time to restart the process, TTL has passed");
synchronized (mLock) {
@@ -218,7 +218,7 @@ final class HotwordDetectionConnection {
HotwordMetricsLogger.writeServiceRestartEvent(mDetectorType,
HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__SCHEDULE);
}
- }, RESTART_PERIOD_SECONDS, RESTART_PERIOD_SECONDS, TimeUnit.SECONDS);
+ }, mReStartPeriodSeconds, mReStartPeriodSeconds, TimeUnit.SECONDS);
}
}
@@ -785,7 +785,7 @@ final class HotwordDetectionConnection {
}
public void dump(String prefix, PrintWriter pw) {
- pw.print(prefix); pw.print("RESTART_PERIOD_SECONDS="); pw.println(RESTART_PERIOD_SECONDS);
+ pw.print(prefix); pw.print("mReStartPeriodSeconds="); pw.println(mReStartPeriodSeconds);
pw.print(prefix);
pw.print("mBound=" + mRemoteHotwordDetectionService.isBound());
pw.print(", mValidatingDspTrigger=" + mValidatingDspTrigger);
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index df114dbabe5b..e0f5b2095190 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -2601,6 +2601,33 @@ public class TelecomManager {
}
/**
+ * Determines whether there are any ongoing {@link PhoneAccount#CAPABILITY_SELF_MANAGED}
+ * calls for a given {@code packageName} and {@code userHandle}.
+ *
+ * @param packageName the package name of the app to check calls for.
+ * @param userHandle the user handle on which to check for calls.
+ * @return {@code true} if there are ongoing calls, {@code false} otherwise.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public boolean isInSelfManagedCall(@NonNull String packageName,
+ @NonNull UserHandle userHandle) {
+ ITelecomService service = getTelecomService();
+ if (service != null) {
+ try {
+ return service.isInSelfManagedCall(packageName, userHandle,
+ mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException isInSelfManagedCall: " + e);
+ e.rethrowFromSystemServer();
+ return false;
+ }
+ } else {
+ throw new IllegalStateException("Telecom service is not present");
+ }
+ }
+
+ /**
* Handles {@link Intent#ACTION_CALL} intents trampolined from UserCallActivity.
* @param intent The {@link Intent#ACTION_CALL} intent to handle.
* @param callingPackageProxy The original package that called this before it was trampolined.
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 9999c897d1b6..07e18d524701 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -22,6 +22,7 @@ import android.telecom.TelecomAnalytics;
import android.telecom.PhoneAccountHandle;
import android.net.Uri;
import android.os.Bundle;
+import android.os.UserHandle;
import android.telecom.PhoneAccount;
/**
@@ -374,4 +375,10 @@ interface ITelecomService {
* @see TelecomServiceImpl#setTestCallDiagnosticService
*/
void setTestCallDiagnosticService(in String packageName);
+
+ /**
+ * @see TelecomServiceImpl#isInSelfManagedCall
+ */
+ boolean isInSelfManagedCall(String packageName, in UserHandle userHandle,
+ String callingPackage);
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 8d7fab4d101f..a2266fb3a929 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1959,6 +1959,13 @@ public class CarrierConfigManager {
"nr_advanced_threshold_bandwidth_khz_int";
/**
+ * Boolean indicating if operator name should be shown in the status bar
+ * @hide
+ */
+ public static final String KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL =
+ "show_operator_name_in_statusbar_bool";
+
+ /**
* The string is used to filter redundant string from PLMN Network Name that's supplied by
* specific carrier.
*
@@ -8913,6 +8920,7 @@ public class CarrierConfigManager {
sDefaults.putStringArray(KEY_SUPPORT_TDSCDMA_ROAMING_NETWORKS_STRING_ARRAY, null);
sDefaults.putBoolean(KEY_WORLD_MODE_ENABLED_BOOL, false);
sDefaults.putString(KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING, "");
+ sDefaults.putBoolean(KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, false);
sDefaults.putBoolean(KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL, false);
sDefaults.putBoolean(KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL, false);
diff --git a/tests/FlickerTests/OWNERS b/tests/FlickerTests/OWNERS
index c1221e3940d2..d40ff5659708 100644
--- a/tests/FlickerTests/OWNERS
+++ b/tests/FlickerTests/OWNERS
@@ -1,4 +1,4 @@
-# Bug component: 909476
+# Bug component: 1157642
include /services/core/java/com/android/server/wm/OWNERS
natanieljr@google.com
pablogamito@google.com
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt
new file mode 100644
index 000000000000..172c4330c3c6
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.server.wm.flicker.helpers
+
+import android.app.Instrumentation
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.parser.toFlickerComponent
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+
+class ImeEditorPopupDialogAppHelper @JvmOverloads constructor(
+ instr: Instrumentation,
+ private val rotation: Int,
+ private val imePackageName: String = IME_PACKAGE,
+ launcherName: String = ActivityOptions.EDITOR_POPUP_DIALOG_ACTIVITY_LAUNCHER_NAME,
+ component: FlickerComponentName =
+ ActivityOptions.EDITOR_POPUP_DIALOG_ACTIVITY_COMPONENT_NAME.toFlickerComponent()
+) : ImeAppHelper(instr, launcherName, component) {
+ override fun openIME(
+ device: UiDevice,
+ wmHelper: WindowManagerStateHelper?
+ ) {
+ val editText = device.wait(Until.findObject(By.text("focused editText")), FIND_TIMEOUT)
+
+ require(editText != null) {
+ "Text field not found, this usually happens when the device " +
+ "was left in an unknown state (e.g. in split screen)"
+ }
+ editText.click()
+ waitIMEShown(device, wmHelper)
+ }
+
+ fun dismissDialog(wmHelper: WindowManagerStateHelper) {
+ val dismissButton = uiDevice.wait(
+ Until.findObject(By.text("Dismiss")), FIND_TIMEOUT)
+
+ // Pressing back key to dismiss the dialog
+ if (dismissButton != null) {
+ dismissButton.click()
+ wmHelper.waitForAppTransitionIdle()
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
new file mode 100644
index 000000000000..bff099e2e7a1
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
@@ -0,0 +1,135 @@
+/*
+ * 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.server.wm.flicker.ime
+
+import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
+import android.view.Surface
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerBuilderProvider
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.annotation.Group4
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.ImeEditorPopupDialogAppHelper
+import com.android.server.wm.flicker.navBarWindowIsVisible
+import com.android.server.wm.flicker.statusBarWindowIsVisible
+import com.android.server.wm.flicker.traces.region.RegionSubject
+import com.android.server.wm.traces.common.FlickerComponentName
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group4
+class CloseImeEditorPopupDialogTest(private val testSpec: FlickerTestParameter) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val imeTestApp = ImeEditorPopupDialogAppHelper(instrumentation, testSpec.startRotation)
+
+ @FlickerBuilderProvider
+ fun buildFlicker(): FlickerBuilder {
+ return FlickerBuilder(instrumentation).apply {
+ setup {
+ eachRun {
+ imeTestApp.launchViaIntent(wmHelper)
+ imeTestApp.openIME(device, wmHelper)
+ }
+ }
+ transitions {
+ imeTestApp.dismissDialog(wmHelper)
+ instrumentation.uiAutomation.syncInputTransactions()
+ }
+ teardown {
+ eachRun {
+ device.pressHome()
+ wmHelper.waitForHomeActivityVisible()
+ imeTestApp.exit()
+ }
+ }
+ }
+ }
+
+ @Postsubmit
+ @Test
+ fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
+
+ @Postsubmit
+ @Test
+ fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
+
+ @Postsubmit
+ @Test
+ fun imeWindowBecameInvisible() = testSpec.imeWindowBecomesInvisible()
+
+ @Postsubmit
+ @Test
+ fun imeLayerAndImeSnapshotVisibleOnScreen() {
+ testSpec.assertLayers {
+ this.isVisible(FlickerComponentName.IME)
+ .then()
+ .isVisible(FlickerComponentName.IME_SNAPSHOT)
+ .then()
+ .isInvisible(FlickerComponentName.IME)
+ }
+ }
+
+ @Postsubmit
+ @Test
+ fun imeSnapshotAssociatedOnAppVisibleRegion() {
+ testSpec.assertLayers {
+ this.invoke("imeSnapshotAssociatedOnAppVisibleRegion") {
+ val imeSnapshotLayers = it.subjects.filter {
+ subject -> subject.name.contains(
+ FlickerComponentName.IME_SNAPSHOT.toLayerName()) && subject.isVisible
+ }
+ if (imeSnapshotLayers.isNotEmpty()) {
+ val visibleAreas = imeSnapshotLayers.mapNotNull { imeSnapshotLayer ->
+ imeSnapshotLayer.layer?.visibleRegion }.toTypedArray()
+ val imeVisibleRegion = RegionSubject.assertThat(visibleAreas, this, timestamp)
+ val appVisibleRegion = it.visibleRegion(imeTestApp.component)
+ if (imeVisibleRegion.region.isNotEmpty) {
+ imeVisibleRegion.coversAtMost(appVisibleRegion.region)
+ }
+ }
+ }
+ }
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance()
+ .getConfigNonRotationTests(
+ repetitions = 2,
+ supportedNavigationModes = listOf(
+ WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
+ WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
+ ),
+ supportedRotations = listOf(Surface.ROTATION_0)
+ )
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
index 7f4966375b98..1b60403ac354 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
@@ -79,7 +79,7 @@ class LaunchAppShowImeAndDialogThemeAppTest(private val testSpec: FlickerTestPar
/**
* Checks that [FlickerComponentName.IME] layer is visible at the end of the transition
*/
- @Presubmit
+ @FlakyTest(bugId = 227142436)
@Test
fun imeLayerExistsEnd() {
testSpec.assertLayersEnd {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
index db2e645fd245..e2e1ae8b90a0 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
@@ -114,7 +114,7 @@ class OpenImeWindowToOverViewTest(private val testSpec: FlickerTestParameter) {
}
}
- @Presubmit
+ @FlakyTest(bugId = 228011606)
@Test
fun imeLayerIsVisibleAndAssociatedWithAppWidow() {
testSpec.assertLayersStart {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
index 7e3ed8252f4c..4b268a871fa0 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
@@ -32,12 +32,15 @@ import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.server.wm.traces.common.WindowManagerConditionsFactory
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import org.junit.Assume
+import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
@@ -55,11 +58,16 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group4
@Presubmit
-class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestParameter) {
+open class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestParameter) {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val testApp = SimpleAppHelper(instrumentation)
private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+ @Before
+ open fun before() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ }
+
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
return FlickerBuilder(instrumentation).apply {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTestShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
index 2252a949ba00..edd52b76cd5c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTestShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2021 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,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.pip
+package com.android.server.wm.flicker.ime
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
@@ -24,22 +24,26 @@ import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import org.junit.Assume
import org.junit.Before
+
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
+/**
+ * Test IME windows switching with 2-Buttons or gestural navigation.
+ * To run this test: `atest FlickerTests:SwitchImeWindowsFromGestureNavTest`
+ */
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group4
-class PipRotationTestShellTransit(testSpec: FlickerTestParameter) : PipRotationTest(testSpec) {
+@FlakyTest(bugId = 228012334)
+class SwitchImeWindowsFromGestureNavTest_ShellTransit(testSpec: FlickerTestParameter)
+ : SwitchImeWindowsFromGestureNavTest(testSpec) {
@Before
override fun before() {
Assume.assumeTrue(isShellTransitionsEnabled)
}
-
- @FlakyTest(bugId = 227214914)
- override fun pipLayerRotates_StartingBounds() = super.pipLayerRotates_StartingBounds()
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index ee0f3d8cd945..6e33f66d111d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -26,12 +26,9 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.LAUNCHER_COMPONENT
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.traces.common.WindowManagerConditionsFactory
-import org.junit.Assume.assumeFalse
-import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -64,10 +61,6 @@ import org.junit.runners.Parameterized
@Group1
open class OpenAppFromOverviewTest(testSpec: FlickerTestParameter)
: OpenAppFromLauncherTransition(testSpec) {
- @Before
- open fun before() {
- assumeFalse(isShellTransitionsEnabled)
- }
/**
* Defines the transition used to run the test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest_ShellTransit.kt
deleted file mode 100644
index 55e1e9ba8557..000000000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest_ShellTransit.kt
+++ /dev/null
@@ -1,80 +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.server.wm.flicker.launch
-
-import androidx.test.filters.FlakyTest
-import android.platform.test.annotations.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.annotation.Group1
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import org.junit.Assume.assumeTrue
-import org.junit.Before
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test launching an app from the recents app view (the overview)
- *
- * To run this test: `atest FlickerTests:OpenAppFromOverviewTest`
- *
- * Actions:
- * Launch [testApp]
- * Press recents
- * Relaunch an app [testApp] by selecting it in the overview screen, and wait animation to
- * complete (only this action is traced)
- *
- * Notes:
- * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
- * are inherited [OpenAppTransition]
- * 2. Part of the test setup occurs automatically via
- * [com.android.server.wm.flicker.TransitionRunnerWithRules],
- * including configuring navigation mode, initial orientation and ensuring no
- * apps are running before setup
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
-class OpenAppFromOverviewTest_ShellTransit(testSpec: FlickerTestParameter)
- : OpenAppFromOverviewTest(testSpec) {
- @Before
- override fun before() {
- assumeTrue(isShellTransitionsEnabled)
- }
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 216266712)
- @Test
- override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow()
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 216266712)
- @Test
- override fun appWindowReplacesLauncherAsTopWindow() =
- super.appWindowReplacesLauncherAsTopWindow()
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 218470989)
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
index fbd611a37d0a..f357177aade2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
@@ -27,10 +27,7 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.traces.common.FlickerComponentName
-import org.junit.Assume
-import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -64,11 +61,6 @@ open class OpenAppNonResizeableTest(testSpec: FlickerTestParameter)
override val testApp = NonResizeableAppHelper(instrumentation)
private val colorFadComponent = FlickerComponentName("", "ColorFade BLAST#")
- @Before
- open fun before() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- }
-
/**
* Checks that the nav bar layer starts invisible, becomes visible during unlocking animation
* and remains visible at the end
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
index 4313b8dbc883..02c1a105949b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
@@ -17,7 +17,6 @@
package com.android.server.wm.flicker.launch
import android.app.Instrumentation
-import androidx.test.filters.FlakyTest
import android.platform.test.annotations.Presubmit
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
@@ -216,7 +215,7 @@ abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) {
* Checks that [testApp] window is not on top at the start of the transition, and then becomes
* the top visible window until the end of the transition.
*/
- @FlakyTest(bugId = 203538234)
+ @Presubmit
@Test
open fun appWindowBecomesTopWindow() {
testSpec.assertWm {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
index 1eb3d8da9f1e..c89e6a44ab6c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -33,12 +33,15 @@ import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
+import org.junit.Assume
+import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -70,6 +73,11 @@ open class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestPa
private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
+ @Before
+ open fun before() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ }
+
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
return FlickerBuilder(instrumentation).apply {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
index 8a08d07e654e..b9fef085da29 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package com.android.server.wm.flicker.launch
+package com.android.server.wm.flicker.quickswitch
-import androidx.test.filters.FlakyTest
import android.platform.test.annotations.RequiresDevice
+import androidx.test.filters.FlakyTest
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.annotation.Group1
@@ -30,30 +30,24 @@ import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
- * Test launching an app while the device is locked
+ * Test quick switching back to previous app from last opened app
*
- * To run this test: `atest FlickerTests:OpenAppNonResizeableTest`
+ * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsBackTest`
*
* Actions:
- * Lock the device.
- * Launch an app on top of the lock screen [testApp] and wait animation to complete
+ * Launch an app [testApp1]
+ * Launch another app [testApp2]
+ * Swipe right from the bottom of the screen to quick switch back to the first app [testApp1]
*
- * Notes:
- * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
- * are inherited [OpenAppTransition]
- * 2. Part of the test setup occurs automatically via
- * [com.android.server.wm.flicker.TransitionRunnerWithRules],
- * including configuring navigation mode, initial orientation and ensuring no
- * apps are running before setup
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group1
-@FlakyTest(bugId = 219688533)
-class OpenAppNonResizeableTest_ShellTransit(testSpec: FlickerTestParameter)
- : OpenAppNonResizeableTest(testSpec) {
+@FlakyTest(bugId = 228009808)
+open class QuickSwitchBetweenTwoAppsBackTest_ShellTransit(testSpec: FlickerTestParameter)
+ : QuickSwitchBetweenTwoAppsBackTest(testSpec) {
@Before
override fun before() {
Assume.assumeTrue(isShellTransitionsEnabled)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
index 5474a42ccf52..725d2c3d818c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -32,6 +32,7 @@ import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
@@ -39,6 +40,8 @@ import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.server.wm.traces.common.Rect
+import org.junit.Assume
+import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -68,6 +71,11 @@ open class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTes
private val testApp1 = SimpleAppHelper(instrumentation)
private val testApp2 = NonResizeableAppHelper(instrumentation)
+ @Before
+ open fun before() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ }
+
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
return FlickerBuilder(instrumentation).apply {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
new file mode 100644
index 000000000000..4b8a8c80cd45
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.quickswitch
+
+import android.platform.test.annotations.RequiresDevice
+import androidx.test.filters.FlakyTest
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test quick switching back to previous app from last opened app
+ *
+ * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsForwardTest`
+ *
+ * Actions:
+ * Launch an app [testApp1]
+ * Launch another app [testApp2]
+ * Swipe right from the bottom of the screen to quick switch back to the first app [testApp1]
+ * Swipe left from the bottom of the screen to quick switch forward to the second app [testApp2]
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+@FlakyTest(bugId = 228009808)
+open class QuickSwitchBetweenTwoAppsForwardTest_ShellTransit(testSpec: FlickerTestParameter)
+ : QuickSwitchBetweenTwoAppsForwardTest(testSpec) {
+ @Before
+ override fun before() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
index f83ae8707e43..cc4a4b2d38aa 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
@@ -28,7 +28,7 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.LAUNCHER_COMPONENT
-import com.android.server.wm.flicker.annotation.Group4
+import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.SimpleAppHelper
@@ -60,7 +60,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group4
+@Group1
class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val taplInstrumentation = LauncherInstrumentation()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index 2b944c666782..8b851e5cfc2b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -25,13 +25,11 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.SimpleAppHelper
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec
import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
-import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Rule
import org.junit.Test
@@ -100,7 +98,7 @@ class ChangeAppRotationTest(
* Windows maybe recreated when rotated. Checks that the focus does not change or if it does,
* focus returns to [testApp]
*/
- @FlakyTest(bugId = 190185577)
+ @Presubmit
@Test
fun focusChanges() {
testSpec.assertEventLog {
@@ -130,18 +128,6 @@ class ChangeAppRotationTest(
@Presubmit
@Test
fun rotationLayerAppearsAndVanishes() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- rotationLayerAppearsAndVanishesAssertion()
- }
-
- /**
- * Checks that the [FlickerComponentName.ROTATION] layer appears during the transition,
- * doesn't flicker, and disappears before the transition is complete
- */
- @FlakyTest(bugId = 218484127)
- @Test
- fun rotationLayerAppearsAndVanishes_shellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
rotationLayerAppearsAndVanishesAssertion()
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index 15fd5e18b233..fac5baf7a2f9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -26,11 +26,8 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.SeamlessRotationAppHelper
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.FlickerComponentName
-import org.junit.Assume
-import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -84,11 +81,6 @@ open class SeamlessAppRotationTest(
) : RotationTransition(testSpec) {
override val testApp = SeamlessRotationAppHelper(instrumentation)
- @Before
- open fun before() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- }
-
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest_ShellTransit.kt
deleted file mode 100644
index d397d5979803..000000000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest_ShellTransit.kt
+++ /dev/null
@@ -1,82 +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.server.wm.flicker.rotation
-
-import androidx.test.filters.FlakyTest
-import android.platform.test.annotations.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.annotation.Group3
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import org.junit.Assume
-import org.junit.Before
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test opening an app and cycling through app rotations using seamless rotations
- *
- * Currently runs:
- * 0 -> 90 degrees
- * 0 -> 90 degrees (with starved UI thread)
- * 90 -> 0 degrees
- * 90 -> 0 degrees (with starved UI thread)
- *
- * Actions:
- * Launch an app in fullscreen and supporting seamless rotation (via intent)
- * Set initial device orientation
- * Start tracing
- * Change device orientation
- * Stop tracing
- *
- * To run this test: `atest FlickerTests:SeamlessAppRotationTest`
- *
- * To run only the presubmit assertions add: `--
- * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
- * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit`
- *
- * To run only the postsubmit assertions add: `--
- * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
- * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit`
- *
- * To run only the flaky assertions add: `--
- * --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest`
- *
- * Notes:
- * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
- * are inherited [RotationTransition]
- * 2. Part of the test setup occurs automatically via
- * [com.android.server.wm.flicker.TransitionRunnerWithRules],
- * including configuring navigation mode, initial orientation and ensuring no
- * apps are running before setup
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group3
-@FlakyTest(bugId = 219689723)
-class SeamlessAppRotationTest_ShellTransit(
- testSpec: FlickerTestParameter
-) : SeamlessAppRotationTest(testSpec) {
- @Before
- override fun before() {
- Assume.assumeTrue(isShellTransitionsEnabled)
- }
-}
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 739fe020d555..7f513b21957f 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -119,5 +119,16 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <activity android:name=".ImeEditorPopupDialogActivity"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.ImeEditorPopupDialogActivity"
+ android:configChanges="orientation|screenSize"
+ android:theme="@style/CutoutShortEdges"
+ android:label="ImeEditorPopupDialogActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 3040a09f2345..18c95cf7bbff 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -56,6 +56,7 @@ public class ActivityOptions {
public static final ComponentName LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME =
new ComponentName(FLICKER_APP_PACKAGE,
FLICKER_APP_PACKAGE + ".LaunchNewTaskActivity");
+
public static final String DIALOG_THEMED_ACTIVITY = "DialogThemedActivity";
public static final ComponentName DIALOG_THEMED_ACTIVITY_COMPONENT_NAME =
new ComponentName(FLICKER_APP_PACKAGE,
@@ -65,4 +66,10 @@ public class ActivityOptions {
public static final ComponentName PORTRAIT_ONLY_ACTIVITY_COMPONENT_NAME =
new ComponentName(FLICKER_APP_PACKAGE,
FLICKER_APP_PACKAGE + ".PortraitOnlyActivity");
+
+ public static final String EDITOR_POPUP_DIALOG_ACTIVITY_LAUNCHER_NAME =
+ "ImeEditorPopupDialogActivity";
+ public static final ComponentName EDITOR_POPUP_DIALOG_ACTIVITY_COMPONENT_NAME =
+ new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".ImeEditorPopupDialogActivity");
}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java
new file mode 100644
index 000000000000..a8613f531e1c
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java
@@ -0,0 +1,46 @@
+/*
+ * 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.server.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.os.Bundle;
+import android.view.WindowManager;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+public class ImeEditorPopupDialogActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ WindowManager.LayoutParams p = getWindow().getAttributes();
+ p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
+ .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+ getWindow().setAttributes(p);
+ LinearLayout layout = new LinearLayout(this);
+ layout.setOrientation(LinearLayout.VERTICAL);
+ setContentView(R.layout.activity_simple);
+
+ final EditText editText = new EditText(this);
+ editText.setHint("focused editText");
+ final AlertDialog dialog = new AlertDialog.Builder(this)
+ .setView(editText)
+ .setPositiveButton("Dismiss", (d, which) -> d.dismiss())
+ .create();
+ dialog.show();
+ }
+}
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index bd0a4bc44e18..bfb32854a374 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -165,6 +165,7 @@ cc_library_host_static {
],
proto: {
export_proto_headers: true,
+ type: "full",
},
defaults: ["aapt2_defaults"],
}