summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp2
-rw-r--r--api/current.txt162
-rw-r--r--api/system-current.txt95
-rw-r--r--api/test-current.txt2
-rw-r--r--cmds/bu/src/com/android/commands/bu/Backup.java72
-rw-r--r--cmds/sm/src/com/android/commands/sm/Sm.java27
-rw-r--r--cmds/statsd/src/atoms.proto37
-rw-r--r--cmds/statsd/src/metrics/CountMetricProducer.cpp2
-rw-r--r--cmds/statsd/src/metrics/DurationMetricProducer.cpp2
-rw-r--r--cmds/statsd/src/metrics/EventMetricProducer.cpp2
-rw-r--r--cmds/statsd/src/metrics/GaugeMetricProducer.cpp2
-rw-r--r--cmds/statsd/src/metrics/ValueMetricProducer.cpp2
-rw-r--r--cmds/statsd/src/stats_log.proto4
-rw-r--r--config/dirty-image-objects2
-rw-r--r--config/hiddenapi-greylist.txt1
-rw-r--r--config/preloaded-classes6
-rw-r--r--core/java/android/app/Activity.java50
-rw-r--r--core/java/android/app/ActivityThread.java42
-rw-r--r--core/java/android/app/ActivityView.java3
-rw-r--r--core/java/android/app/ApplicationPackageManager.java18
-rw-r--r--core/java/android/app/AutomaticZenRule.java90
-rw-r--r--core/java/android/app/INotificationManager.aidl1
-rw-r--r--core/java/android/app/NotificationManager.java95
-rw-r--r--core/java/android/app/SystemServiceRegistry.java8
-rw-r--r--core/java/android/app/backup/IBackupManager.aidl13
-rw-r--r--core/java/android/app/job/JobInfo.java7
-rw-r--r--core/java/android/app/role/IRoleManager.aidl2
-rw-r--r--core/java/android/app/role/RoleManager.java21
-rw-r--r--core/java/android/content/ComponentName.java11
-rw-r--r--core/java/android/content/Context.java13
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl5
-rw-r--r--core/java/android/content/pm/ModuleInfo.aidl19
-rw-r--r--core/java/android/hardware/ISensorPrivacyListener.aidl29
-rw-r--r--core/java/android/hardware/ISensorPrivacyManager.aidl35
-rw-r--r--core/java/android/hardware/SensorPrivacyManager.java171
-rw-r--r--core/java/android/inputmethodservice/Keyboard.java11
-rw-r--r--core/java/android/inputmethodservice/KeyboardView.java6
-rw-r--r--core/java/android/os/FileUtils.java30
-rw-r--r--core/java/android/os/SystemProperties.java28
-rw-r--r--core/java/android/os/storage/StorageManager.java4
-rw-r--r--core/java/android/provider/DeviceConfig.java275
-rw-r--r--core/java/android/provider/Settings.java71
-rw-r--r--core/java/android/service/contentcapture/ContentCaptureService.java42
-rw-r--r--core/java/android/service/contentcapture/IContentCaptureService.aidl4
-rw-r--r--core/java/android/service/contentcapture/InteractionContext.java157
-rw-r--r--core/java/android/service/notification/Condition.java8
-rw-r--r--core/java/android/service/notification/ConditionProviderService.java29
-rw-r--r--core/java/android/service/notification/NotificationListenerService.java40
-rw-r--r--core/java/android/service/notification/NotificationRankingUpdate.java14
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java39
-rw-r--r--core/java/android/text/style/LineBackgroundSpan.java2
-rw-r--r--core/java/android/view/IWindow.aidl35
-rw-r--r--core/java/android/view/NotificationHeaderView.java2
-rw-r--r--core/java/android/view/View.java100
-rw-r--r--core/java/android/view/ViewRootImpl.java35
-rw-r--r--core/java/android/view/contentcapture/ContentCaptureContext.aidl (renamed from core/java/android/service/contentcapture/InteractionContext.aidl)4
-rw-r--r--core/java/android/view/contentcapture/ContentCaptureContext.java333
-rw-r--r--core/java/android/view/contentcapture/ContentCaptureEvent.java65
-rw-r--r--core/java/android/view/contentcapture/ContentCaptureManager.java502
-rw-r--r--core/java/android/view/contentcapture/ContentCaptureSession.java570
-rw-r--r--core/java/android/view/contentcapture/ContentCaptureSessionId.java (renamed from core/java/android/service/contentcapture/InteractionSessionId.java)24
-rw-r--r--core/java/android/view/contentcapture/IContentCaptureManager.aidl4
-rw-r--r--core/java/android/view/contentcapture/UserDataRemovalRequest.java167
-rw-r--r--core/java/android/webkit/WebResourceResponse.java23
-rw-r--r--core/java/android/widget/TextView.java48
-rw-r--r--core/java/com/android/internal/os/BinderCallsStats.java12
-rw-r--r--core/java/com/android/internal/os/LooperStats.java16
-rw-r--r--core/java/com/android/internal/util/ArrayUtils.java7
-rw-r--r--core/java/com/android/internal/view/BaseIWindow.java4
-rw-r--r--core/jni/android/graphics/Paint.cpp14
-rw-r--r--core/jni/fd_utils.cpp6
-rw-r--r--core/proto/android/server/jobscheduler.proto11
-rw-r--r--core/proto/android/stats/devicepolicy/device_policy_enums.proto3
-rw-r--r--core/res/AndroidManifest.xml4
-rw-r--r--core/res/res/values/attrs.xml106
-rw-r--r--core/res/res/values/config.xml96
-rw-r--r--core/res/res/values/public.xml4
-rw-r--r--core/res/res/values/strings.xml2
-rw-r--r--core/res/res/values/styles_device_defaults.xml10
-rw-r--r--core/res/res/values/symbols.xml12
-rw-r--r--core/res/res/values/themes_device_defaults.xml5
-rw-r--r--core/tests/coretests/src/android/os/OsTests.java1
-rw-r--r--core/tests/coretests/src/android/os/SystemPropertiesTest.java51
-rw-r--r--core/tests/coretests/src/android/provider/DeviceConfigTest.java166
-rw-r--r--core/tests/coretests/src/android/provider/SettingsBackupTest.java9
-rw-r--r--core/tests/coretests/src/android/provider/SettingsProviderTest.java41
-rw-r--r--core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java3
-rw-r--r--core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java6
-rw-r--r--core/tests/systemproperties/src/android/os/SystemPropertiesTest.java29
-rw-r--r--data/etc/Android.mk19
-rw-r--r--data/etc/com.android.settings.xml53
-rw-r--r--data/etc/com.android.systemui.xml62
-rw-r--r--data/etc/privapp-permissions-platform.xml80
-rw-r--r--libs/hwui/DeviceInfo.cpp46
-rw-r--r--libs/hwui/DeviceInfo.h7
-rw-r--r--libs/hwui/hwui/Canvas.cpp2
-rw-r--r--libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp11
-rw-r--r--libs/hwui/pipeline/skia/SkiaPipeline.cpp15
-rw-r--r--libs/hwui/pipeline/skia/SkiaPipeline.h2
-rw-r--r--libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp11
-rw-r--r--libs/hwui/renderthread/EglManager.cpp178
-rw-r--r--libs/hwui/renderthread/EglManager.h18
-rw-r--r--libs/hwui/renderthread/VulkanManager.cpp28
-rw-r--r--libs/hwui/renderthread/VulkanManager.h12
-rw-r--r--libs/hwui/tests/unit/RenderNodeTests.cpp1
-rw-r--r--location/java/android/location/ILocationManager.aidl4
-rw-r--r--location/java/android/location/LocationManager.java61
-rw-r--r--media/java/android/media/DataSourceCallback.java2
-rw-r--r--media/java/android/media/MediaDataSource.java2
-rw-r--r--media/java/android/media/session/ISession.aidl2
-rw-r--r--media/java/android/media/session/MediaSession.java14
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java17
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java170
-rw-r--r--packages/SettingsLib/ActionButtonsPreference/Android.bp2
-rw-r--r--packages/SettingsLib/Android.bp4
-rw-r--r--packages/SettingsLib/LayoutPreference/Android.bp (renamed from packages/SettingsLib/SettingsLayoutPreference/Android.bp)2
-rw-r--r--packages/SettingsLib/LayoutPreference/AndroidManifest.xml (renamed from packages/SettingsLib/SettingsLayoutPreference/AndroidManifest.xml)0
-rw-r--r--packages/SettingsLib/LayoutPreference/res/layout/layout_preference_frame.xml (renamed from packages/SettingsLib/SettingsLayoutPreference/res/layout/layout_preference_frame.xml)0
-rw-r--r--packages/SettingsLib/LayoutPreference/res/layout/settings_entity_header.xml (renamed from packages/SettingsLib/SettingsLayoutPreference/res/layout/settings_entity_header.xml)0
-rw-r--r--packages/SettingsLib/LayoutPreference/res/values/styles.xml (renamed from packages/SettingsLib/SettingsLayoutPreference/res/values/styles.xml)0
-rw-r--r--packages/SettingsLib/LayoutPreference/src/com/android/settingslib/widget/LayoutPreference.java (renamed from packages/SettingsLib/SettingsLayoutPreference/src/com/android/settingslib/widget/LayoutPreference.java)0
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java4
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java66
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java12
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java27
-rw-r--r--packages/SystemUI/Android.bp11
-rw-r--r--packages/SystemUI/AndroidManifest.xml1
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml2
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml2
-rw-r--r--packages/SystemUI/res-keyguard/values/styles.xml8
-rw-r--r--packages/SystemUI/res/drawable/ic_5g_mobiledata.xml27
-rw-r--r--packages/SystemUI/res/drawable/ic_5g_plus_mobiledata.xml33
-rw-r--r--packages/SystemUI/res/drawable/ic_signal_sensors.xml28
-rw-r--r--packages/SystemUI/res/values/config.xml2
-rw-r--r--packages/SystemUI/res/values/strings.xml12
-rw-r--r--packages/SystemUI/res/values/styles.xml2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/PasswordTextView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/Dependency.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyTile.java126
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/AlertTransferListener.java41
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java58
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java181
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java39
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java62
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java55
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/SensorPrivacyTileTest.java115
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java46
-rw-r--r--proto/src/metrics_constants/metrics_constants.proto7
-rw-r--r--services/backup/java/com/android/server/backup/BackupManagerService.java7
-rw-r--r--services/backup/java/com/android/server/backup/Trampoline.java15
-rw-r--r--services/backup/java/com/android/server/backup/UserBackupManagerService.java7
-rw-r--r--services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java6
-rw-r--r--services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java41
-rw-r--r--services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java (renamed from services/contentcapture/java/com/android/server/contentcapture/ContentCaptureSession.java)27
-rw-r--r--services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java14
-rw-r--r--services/core/java/com/android/server/LocationManagerService.java43
-rw-r--r--services/core/java/com/android/server/SensorPrivacyService.java426
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java73
-rw-r--r--services/core/java/com/android/server/TelephonyRegistry.java7
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java3
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerConstants.java8
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java119
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java13
-rw-r--r--services/core/java/com/android/server/am/EventLogTags.logtags5
-rw-r--r--services/core/java/com/android/server/am/MemoryStatUtil.java23
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java5
-rw-r--r--services/core/java/com/android/server/display/AutomaticBrightnessController.java85
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java41
-rw-r--r--services/core/java/com/android/server/display/HysteresisLevels.java74
-rw-r--r--services/core/java/com/android/server/job/JobSchedulerService.java66
-rw-r--r--services/core/java/com/android/server/job/controllers/QuotaController.java372
-rw-r--r--services/core/java/com/android/server/job/controllers/TimeController.java96
-rw-r--r--services/core/java/com/android/server/location/GnssNetworkConnectivityHandler.java99
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java28
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java46
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecord.java12
-rw-r--r--services/core/java/com/android/server/notification/ZenModeConditions.java61
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java148
-rw-r--r--services/core/java/com/android/server/pm/ModuleInfoProvider.java178
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java3
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java210
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java22
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java28
-rw-r--r--services/core/java/com/android/server/policy/WindowManagerPolicy.java7
-rw-r--r--services/core/java/com/android/server/role/RoleManagerService.java12
-rw-r--r--services/core/java/com/android/server/role/RoleManagerShellCommand.java4
-rw-r--r--services/core/java/com/android/server/role/RoleUserState.java16
-rw-r--r--services/core/java/com/android/server/stats/StatsCompanionService.java4
-rw-r--r--services/core/java/com/android/server/wm/ActivityDisplay.java14
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java43
-rw-r--r--services/core/java/com/android/server/wm/AppWindowToken.java12
-rw-r--r--services/core/java/com/android/server/wm/BoundsAnimationController.java7
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java42
-rw-r--r--services/core/java/com/android/server/wm/LaunchParamsController.java31
-rw-r--r--services/core/java/com/android/server/wm/RecentsAnimation.java10
-rw-r--r--services/core/java/com/android/server/wm/RootActivityContainer.java38
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java53
-rw-r--r--services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java35
-rw-r--r--services/core/java/com/android/server/wm/TaskTapPointerEventListener.java43
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java29
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java32
-rw-r--r--services/core/jni/com_android_server_location_GnssLocationProvider.cpp83
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java324
-rw-r--r--services/java/com/android/server/SystemServer.java4
-rw-r--r--services/robotests/Android.mk1
-rw-r--r--services/robotests/src/com/android/commands/bu/AdbBackupTest.java61
-rw-r--r--services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java127
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java523
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java886
-rw-r--r--services/tests/servicestests/res/values/strings.xml2
-rw-r--r--services/tests/servicestests/res/xml/unparseable_metadata1.xml4
-rw-r--r--services/tests/servicestests/res/xml/unparseable_metadata2.xml4
-rw-r--r--services/tests/servicestests/res/xml/well_formed_metadata.xml4
-rw-r--r--services/tests/servicestests/res/xml/well_formed_metadata2.xml5
-rw-r--r--services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java39
-rw-r--r--services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java19
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyEventLoggerTest.java114
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java79
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java130
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java21
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java23
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java63
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java12
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java27
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java69
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java67
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java17
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TestIWindow.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java2
-rw-r--r--startop/view_compiler/dex_builder.cc40
-rw-r--r--startop/view_compiler/dex_builder.h59
-rw-r--r--startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java27
-rw-r--r--startop/view_compiler/dex_testcase_generator.cc53
-rw-r--r--telecomm/java/android/telecom/DefaultDialerManager.java2
-rw-r--r--telecomm/java/android/telecom/VideoProfile.java11
-rw-r--r--telephony/java/android/telephony/NetworkService.java18
-rw-r--r--telephony/java/android/telephony/SubscriptionInfo.java38
-rw-r--r--telephony/java/android/telephony/SubscriptionManager.java51
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java4
-rw-r--r--telephony/java/android/telephony/data/DataService.java18
-rw-r--r--telephony/java/android/telephony/data/QualifiedNetworksService.java2
-rw-r--r--telephony/java/com/android/internal/telephony/DctConstants.java7
-rwxr-xr-xtelephony/java/com/android/internal/telephony/ISub.aidl4
-rw-r--r--wifi/java/android/net/wifi/IWifiManager.aidl2
-rw-r--r--wifi/java/android/net/wifi/WifiManager.java27
-rw-r--r--wifi/java/android/net/wifi/aware/DiscoverySession.java14
-rw-r--r--wifi/java/android/net/wifi/aware/WifiAwareManager.java152
-rw-r--r--wifi/java/android/net/wifi/aware/WifiAwareNetworkInfo.java135
-rw-r--r--wifi/java/android/net/wifi/aware/WifiAwareSession.java6
-rw-r--r--wifi/java/android/net/wifi/hotspot2/OsuProvider.java92
-rw-r--r--wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java69
-rw-r--r--wifi/java/com/android/server/wifi/AbstractWifiService.java7
-rw-r--r--wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java158
-rw-r--r--wifi/tests/src/android/net/wifi/hotspot2/OsuProviderTest.java26
-rw-r--r--wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java16
270 files changed, 10059 insertions, 2781 deletions
diff --git a/Android.bp b/Android.bp
index 5a1b813ba628..1b81306b3699 100644
--- a/Android.bp
+++ b/Android.bp
@@ -207,6 +207,8 @@ java_defaults {
"core/java/android/hardware/usb/IUsbSerialReader.aidl",
"core/java/android/net/ICaptivePortal.aidl",
"core/java/android/net/IConnectivityManager.aidl",
+ "core/java/android/hardware/ISensorPrivacyListener.aidl",
+ "core/java/android/hardware/ISensorPrivacyManager.aidl",
"core/java/android/net/IIpConnectivityMetrics.aidl",
"core/java/android/net/IEthernetManager.aidl",
"core/java/android/net/IEthernetServiceListener.aidl",
diff --git a/api/current.txt b/api/current.txt
index 63bbad29cc0a..d60c1cc35dee 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -417,7 +417,7 @@ package android {
field public static final int clipOrientation = 16843274; // 0x101020a
field public static final int clipToPadding = 16842987; // 0x10100eb
field public static final int closeIcon = 16843905; // 0x1010481
- field public static final int codes = 16843330; // 0x1010242
+ field public static final deprecated int codes = 16843330; // 0x1010242
field public static final int collapseColumns = 16843083; // 0x101014b
field public static final int collapseContentDescription = 16843984; // 0x10104d0
field public static final int collapseIcon = 16844031; // 0x10104ff
@@ -718,7 +718,7 @@ package android {
field public static final int homeAsUpIndicator = 16843531; // 0x101030b
field public static final int homeLayout = 16843549; // 0x101031d
field public static final int horizontalDivider = 16843053; // 0x101012d
- field public static final int horizontalGap = 16843327; // 0x101023f
+ field public static final deprecated int horizontalGap = 16843327; // 0x101023f
field public static final int horizontalScrollViewStyle = 16843603; // 0x1010353
field public static final int horizontalSpacing = 16843028; // 0x1010114
field public static final int host = 16842792; // 0x1010028
@@ -726,7 +726,7 @@ package android {
field public static final int hotSpotY = 16844056; // 0x1010518
field public static final int hyphenationFrequency = 16843998; // 0x10104de
field public static final int icon = 16842754; // 0x1010002
- field public static final int iconPreview = 16843337; // 0x1010249
+ field public static final deprecated int iconPreview = 16843337; // 0x1010249
field public static final int iconSpaceReserved = 16844129; // 0x1010561
field public static final int iconTint = 16844126; // 0x101055e
field public static final int iconTintMode = 16844127; // 0x101055f
@@ -787,12 +787,12 @@ package android {
field public static final int isGame = 16843764; // 0x10103f4
field public static final int isIndicator = 16843079; // 0x1010147
field public static final int isLightTheme = 16844176; // 0x1010590
- field public static final int isModifier = 16843334; // 0x1010246
- field public static final int isRepeatable = 16843336; // 0x1010248
+ field public static final deprecated int isModifier = 16843334; // 0x1010246
+ field public static final deprecated int isRepeatable = 16843336; // 0x1010248
field public static final int isScrollContainer = 16843342; // 0x101024e
field public static final int isSplitRequired = 16844177; // 0x1010591
field public static final int isStatic = 16844122; // 0x101055a
- field public static final int isSticky = 16843335; // 0x1010247
+ field public static final deprecated int isSticky = 16843335; // 0x1010247
field public static final int isolatedProcess = 16843689; // 0x10103a9
field public static final int isolatedSplits = 16844107; // 0x101054b
field public static final int itemBackground = 16843056; // 0x1010130
@@ -802,27 +802,27 @@ package android {
field public static final int justificationMode = 16844135; // 0x1010567
field public static final int keepScreenOn = 16843286; // 0x1010216
field public static final int key = 16843240; // 0x10101e8
- field public static final int keyBackground = 16843315; // 0x1010233
- field public static final int keyEdgeFlags = 16843333; // 0x1010245
- field public static final int keyHeight = 16843326; // 0x101023e
- field public static final int keyIcon = 16843340; // 0x101024c
- field public static final int keyLabel = 16843339; // 0x101024b
- field public static final int keyOutputText = 16843338; // 0x101024a
- field public static final int keyPreviewHeight = 16843321; // 0x1010239
- field public static final int keyPreviewLayout = 16843319; // 0x1010237
- field public static final int keyPreviewOffset = 16843320; // 0x1010238
+ field public static final deprecated int keyBackground = 16843315; // 0x1010233
+ field public static final deprecated int keyEdgeFlags = 16843333; // 0x1010245
+ field public static final deprecated int keyHeight = 16843326; // 0x101023e
+ field public static final deprecated int keyIcon = 16843340; // 0x101024c
+ field public static final deprecated int keyLabel = 16843339; // 0x101024b
+ field public static final deprecated int keyOutputText = 16843338; // 0x101024a
+ field public static final deprecated int keyPreviewHeight = 16843321; // 0x1010239
+ field public static final deprecated int keyPreviewLayout = 16843319; // 0x1010237
+ field public static final deprecated int keyPreviewOffset = 16843320; // 0x1010238
field public static final int keySet = 16843739; // 0x10103db
- field public static final int keyTextColor = 16843318; // 0x1010236
- field public static final int keyTextSize = 16843316; // 0x1010234
- field public static final int keyWidth = 16843325; // 0x101023d
+ field public static final deprecated int keyTextColor = 16843318; // 0x1010236
+ field public static final deprecated int keyTextSize = 16843316; // 0x1010234
+ field public static final deprecated int keyWidth = 16843325; // 0x101023d
field public static final int keyboardLayout = 16843691; // 0x10103ab
- field public static final int keyboardMode = 16843341; // 0x101024d
+ field public static final deprecated int keyboardMode = 16843341; // 0x101024d
field public static final int keyboardNavigationCluster = 16844096; // 0x1010540
field public static final int keycode = 16842949; // 0x10100c5
field public static final int killAfterRestore = 16843420; // 0x101029c
field public static final int label = 16842753; // 0x1010001
field public static final int labelFor = 16843718; // 0x10103c6
- field public static final int labelTextSize = 16843317; // 0x1010235
+ field public static final deprecated int labelTextSize = 16843317; // 0x1010235
field public static final int languageTag = 16844040; // 0x1010508
field public static final int largeHeap = 16843610; // 0x101035a
field public static final int largeScreens = 16843398; // 0x1010286
@@ -1047,12 +1047,12 @@ package android {
field public static final int pointerIcon = 16844041; // 0x1010509
field public static final int popupAnimationStyle = 16843465; // 0x10102c9
field public static final int popupBackground = 16843126; // 0x1010176
- field public static final int popupCharacters = 16843332; // 0x1010244
+ field public static final deprecated int popupCharacters = 16843332; // 0x1010244
field public static final int popupElevation = 16843916; // 0x101048c
field public static final int popupEnterTransition = 16844063; // 0x101051f
field public static final int popupExitTransition = 16844064; // 0x1010520
- field public static final int popupKeyboard = 16843331; // 0x1010243
- field public static final int popupLayout = 16843323; // 0x101023b
+ field public static final deprecated int popupKeyboard = 16843331; // 0x1010243
+ field public static final deprecated int popupLayout = 16843323; // 0x101023b
field public static final int popupMenuStyle = 16843520; // 0x1010300
field public static final int popupTheme = 16843945; // 0x10104a9
field public static final int popupWindowStyle = 16842870; // 0x1010076
@@ -1151,7 +1151,7 @@ package android {
field public static final int roundIcon = 16844076; // 0x101052c
field public static final int rowCount = 16843637; // 0x1010375
field public static final int rowDelay = 16843216; // 0x10101d0
- field public static final int rowEdgeFlags = 16843329; // 0x1010241
+ field public static final deprecated int rowEdgeFlags = 16843329; // 0x1010241
field public static final int rowHeight = 16843058; // 0x1010132
field public static final int rowOrderPreserved = 16843638; // 0x1010376
field public static final int saveEnabled = 16842983; // 0x10100e7
@@ -1287,7 +1287,7 @@ package android {
field public static final int state_focused = 16842908; // 0x101009c
field public static final int state_hovered = 16843623; // 0x1010367
field public static final int state_last = 16842918; // 0x10100a6
- field public static final int state_long_pressable = 16843324; // 0x101023c
+ field public static final deprecated int state_long_pressable = 16843324; // 0x101023c
field public static final int state_middle = 16842917; // 0x10100a5
field public static final int state_multiline = 16843597; // 0x101034d
field public static final int state_pressed = 16842919; // 0x10100a7
@@ -1525,9 +1525,9 @@ package android {
field public static final int versionCodeMajor = 16844150; // 0x1010576
field public static final int versionMajor = 16844151; // 0x1010577
field public static final int versionName = 16843292; // 0x101021c
- field public static final int verticalCorrection = 16843322; // 0x101023a
+ field public static final deprecated int verticalCorrection = 16843322; // 0x101023a
field public static final int verticalDivider = 16843054; // 0x101012e
- field public static final int verticalGap = 16843328; // 0x1010240
+ field public static final deprecated int verticalGap = 16843328; // 0x1010240
field public static final int verticalScrollbarPosition = 16843572; // 0x1010334
field public static final int verticalSpacing = 16843029; // 0x1010115
field public static final int viewportHeight = 16843779; // 0x1010403
@@ -1895,7 +1895,7 @@ package android {
field public static final int input = 16908297; // 0x1020009
field public static final int inputArea = 16908318; // 0x102001e
field public static final int inputExtractEditText = 16908325; // 0x1020025
- field public static final int keyboardView = 16908326; // 0x1020026
+ field public static final deprecated int keyboardView = 16908326; // 0x1020026
field public static final int list = 16908298; // 0x102000a
field public static final int list_container = 16908351; // 0x102003f
field public static final int mask = 16908334; // 0x102002e
@@ -2606,7 +2606,7 @@ package android {
field public static final int Widget_Holo_WebView = 16973993; // 0x10300a9
field public static final int Widget_ImageButton = 16973862; // 0x1030026
field public static final int Widget_ImageWell = 16973861; // 0x1030025
- field public static final int Widget_KeyboardView = 16973911; // 0x1030057
+ field public static final deprecated int Widget_KeyboardView = 16973911; // 0x1030057
field public static final int Widget_ListPopupWindow = 16973957; // 0x1030085
field public static final int Widget_ListView = 16973870; // 0x103002e
field public static final int Widget_ListView_DropDown = 16973872; // 0x1030030
@@ -4433,11 +4433,12 @@ package android.app {
}
public final class AutomaticZenRule implements android.os.Parcelable {
- ctor public AutomaticZenRule(java.lang.String, android.content.ComponentName, android.net.Uri, int, boolean);
- ctor public AutomaticZenRule(java.lang.String, android.content.ComponentName, android.net.Uri, android.service.notification.ZenPolicy, boolean);
+ ctor public deprecated AutomaticZenRule(java.lang.String, android.content.ComponentName, android.net.Uri, int, boolean);
+ ctor public AutomaticZenRule(java.lang.String, android.content.ComponentName, android.content.ComponentName, android.net.Uri, android.service.notification.ZenPolicy, int, boolean);
ctor public AutomaticZenRule(android.os.Parcel);
method public int describeContents();
method public android.net.Uri getConditionId();
+ method public android.content.ComponentName getConfigurationActivity();
method public long getCreationTime();
method public int getInterruptionFilter();
method public java.lang.String getName();
@@ -4445,6 +4446,7 @@ package android.app {
method public android.service.notification.ZenPolicy getZenPolicy();
method public boolean isEnabled();
method public void setConditionId(android.net.Uri);
+ method public void setConfigurationActivity(android.content.ComponentName);
method public void setEnabled(boolean);
method public void setInterruptionFilter(int);
method public void setName(java.lang.String);
@@ -5773,16 +5775,19 @@ package android.app {
method public void notifyAsPackage(java.lang.String, java.lang.String, int, android.app.Notification);
method public boolean removeAutomaticZenRule(java.lang.String);
method public void revokeNotificationDelegate();
+ method public void setAutomaticZenRuleState(java.lang.String, android.service.notification.Condition);
method public final void setInterruptionFilter(int);
method public void setNotificationDelegate(java.lang.String);
method public void setNotificationPolicy(android.app.NotificationManager.Policy);
method public boolean updateAutomaticZenRule(java.lang.String, android.app.AutomaticZenRule);
field public static final java.lang.String ACTION_APP_BLOCK_STATE_CHANGED = "android.app.action.APP_BLOCK_STATE_CHANGED";
+ field public static final java.lang.String ACTION_AUTOMATIC_ZEN_RULE = "android.app.action.AUTOMATIC_ZEN_RULE";
field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
field public static final java.lang.String ACTION_NOTIFICATION_POLICY_CHANGED = "android.app.action.NOTIFICATION_POLICY_CHANGED";
+ field public static final java.lang.String EXTRA_AUTOMATIC_RULE_ID = "android.app.extra.AUTOMATIC_RULE_ID";
field public static final java.lang.String EXTRA_BLOCKED_STATE = "android.app.extra.BLOCKED_STATE";
field public static final java.lang.String EXTRA_NOTIFICATION_CHANNEL_GROUP_ID = "android.app.extra.NOTIFICATION_CHANNEL_GROUP_ID";
field public static final java.lang.String EXTRA_NOTIFICATION_CHANNEL_ID = "android.app.extra.NOTIFICATION_CHANNEL_ID";
@@ -5798,6 +5803,8 @@ package android.app {
field public static final int INTERRUPTION_FILTER_NONE = 3; // 0x3
field public static final int INTERRUPTION_FILTER_PRIORITY = 2; // 0x2
field public static final int INTERRUPTION_FILTER_UNKNOWN = 0; // 0x0
+ field public static final java.lang.String META_DATA_AUTOMATIC_RULE_TYPE = "android.app.automatic.ruleType";
+ field public static final java.lang.String META_DATA_RULE_INSTANCE_LIMIT = "android.app.zen.automatic.ruleInstanceLimit";
}
public static class NotificationManager.Policy implements android.os.Parcelable {
@@ -22382,7 +22389,7 @@ package android.inputmethodservice {
field public int visibleTopInsets;
}
- public class Keyboard {
+ public deprecated class Keyboard {
ctor public Keyboard(android.content.Context, int);
ctor public Keyboard(android.content.Context, int, int, int, int);
ctor public Keyboard(android.content.Context, int, int);
@@ -22456,7 +22463,7 @@ package android.inputmethodservice {
field public int verticalGap;
}
- public class KeyboardView extends android.view.View implements android.view.View.OnClickListener {
+ public deprecated class KeyboardView extends android.view.View implements android.view.View.OnClickListener {
ctor public KeyboardView(android.content.Context, android.util.AttributeSet);
ctor public KeyboardView(android.content.Context, android.util.AttributeSet, int);
ctor public KeyboardView(android.content.Context, android.util.AttributeSet, int, int);
@@ -29677,8 +29684,8 @@ package android.net.wifi.aware {
public class DiscoverySession implements java.lang.AutoCloseable {
method public void close();
- method public android.net.NetworkSpecifier createNetworkSpecifierOpen(android.net.wifi.aware.PeerHandle);
- method public android.net.NetworkSpecifier createNetworkSpecifierPassphrase(android.net.wifi.aware.PeerHandle, java.lang.String);
+ method public deprecated android.net.NetworkSpecifier createNetworkSpecifierOpen(android.net.wifi.aware.PeerHandle);
+ method public deprecated android.net.NetworkSpecifier createNetworkSpecifierPassphrase(android.net.wifi.aware.PeerHandle, java.lang.String);
method public void sendMessage(android.net.wifi.aware.PeerHandle, int, byte[]);
}
@@ -29763,6 +29770,21 @@ package android.net.wifi.aware {
field public static final int WIFI_AWARE_DATA_PATH_ROLE_RESPONDER = 1; // 0x1
}
+ public static class WifiAwareManager.NetworkSpecifierBuilder {
+ ctor public WifiAwareManager.NetworkSpecifierBuilder();
+ method public android.net.NetworkSpecifier build();
+ method public android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder setDiscoverySession(android.net.wifi.aware.DiscoverySession);
+ method public android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder setPeerHandle(android.net.wifi.aware.PeerHandle);
+ method public android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder setPskPassphrase(java.lang.String);
+ }
+
+ public final class WifiAwareNetworkInfo implements android.os.Parcelable android.net.TransportInfo {
+ method public int describeContents();
+ method public java.net.Inet6Address getPeerIpv6Addr();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.net.wifi.aware.WifiAwareNetworkInfo> CREATOR;
+ }
+
public class WifiAwareSession implements java.lang.AutoCloseable {
method public void close();
method public android.net.NetworkSpecifier createNetworkSpecifierOpen(int, byte[]);
@@ -40994,10 +41016,10 @@ package android.service.notification {
field public final java.lang.String summary;
}
- public abstract class ConditionProviderService extends android.app.Service {
+ public abstract deprecated class ConditionProviderService extends android.app.Service {
ctor public ConditionProviderService();
- method public final void notifyCondition(android.service.notification.Condition);
- method public final void notifyConditions(android.service.notification.Condition...);
+ method public final deprecated void notifyCondition(android.service.notification.Condition);
+ method public final deprecated void notifyConditions(android.service.notification.Condition...);
method public android.os.IBinder onBind(android.content.Intent);
method public abstract void onConnected();
method public void onRequestConditions(int);
@@ -41005,10 +41027,10 @@ package android.service.notification {
method public abstract void onUnsubscribe(android.net.Uri);
method public static final void requestRebind(android.content.ComponentName);
method public final void requestUnbind();
- field public static final java.lang.String EXTRA_RULE_ID = "android.service.notification.extra.RULE_ID";
- field public static final java.lang.String META_DATA_CONFIGURATION_ACTIVITY = "android.service.zen.automatic.configurationActivity";
- field public static final java.lang.String META_DATA_RULE_INSTANCE_LIMIT = "android.service.zen.automatic.ruleInstanceLimit";
- field public static final java.lang.String META_DATA_RULE_TYPE = "android.service.zen.automatic.ruleType";
+ field public static final deprecated java.lang.String EXTRA_RULE_ID = "android.service.notification.extra.RULE_ID";
+ field public static final deprecated java.lang.String META_DATA_CONFIGURATION_ACTIVITY = "android.service.zen.automatic.configurationActivity";
+ field public static final deprecated java.lang.String META_DATA_RULE_INSTANCE_LIMIT = "android.service.zen.automatic.ruleInstanceLimit";
+ field public static final deprecated java.lang.String META_DATA_RULE_TYPE = "android.service.zen.automatic.ruleType";
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.ConditionProviderService";
}
@@ -41084,12 +41106,12 @@ package android.service.notification {
public static class NotificationListenerService.Ranking {
ctor public NotificationListenerService.Ranking();
- method public boolean audiblyAlerted();
method public boolean canShowBadge();
method public android.app.NotificationChannel getChannel();
method public int getImportance();
method public java.lang.CharSequence getImportanceExplanation();
method public java.lang.String getKey();
+ method public long getLastAudiblyAlertedMillis();
method public java.lang.String getOverrideGroupKey();
method public int getRank();
method public int getSuppressedVisualEffects();
@@ -42225,7 +42247,9 @@ package android.system {
field public static final int SIOCGIFBRDADDR;
field public static final int SIOCGIFDSTADDR;
field public static final int SIOCGIFNETMASK;
+ field public static final int SOCK_CLOEXEC;
field public static final int SOCK_DGRAM;
+ field public static final int SOCK_NONBLOCK;
field public static final int SOCK_RAW;
field public static final int SOCK_SEQPACKET;
field public static final int SOCK_STREAM;
@@ -43334,9 +43358,12 @@ package android.telecom {
public static final class VideoProfile.CameraCapabilities implements android.os.Parcelable {
ctor public VideoProfile.CameraCapabilities(int, int);
+ ctor public VideoProfile.CameraCapabilities(int, int, boolean, float);
method public int describeContents();
method public int getHeight();
+ method public float getMaxZoom();
method public int getWidth();
+ method public boolean isZoomSupported();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.telecom.VideoProfile.CameraCapabilities> CREATOR;
}
@@ -44246,6 +44273,7 @@ package android.telephony {
public class SubscriptionInfo implements android.os.Parcelable {
method public android.graphics.Bitmap createIconBitmap(android.content.Context);
method public int describeContents();
+ method public int getCarrierId();
method public java.lang.CharSequence getCarrierName();
method public java.lang.String getCountryIso();
method public int getDataRoaming();
@@ -44293,6 +44321,8 @@ package android.telephony {
method public void removeOnOpportunisticSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener);
method public void removeOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
method public boolean removeSubscriptionsFromGroup(int[]);
+ method public boolean setMetered(boolean, int);
+ method public boolean setOpportunistic(boolean, int);
method public java.lang.String setSubscriptionGroup(int[]);
method public void setSubscriptionOverrideCongested(int, boolean, long);
method public void setSubscriptionOverrideUnmetered(int, boolean, long);
@@ -49548,6 +49578,7 @@ package android.view {
method public android.graphics.Rect getClipBounds();
method public boolean getClipBounds(android.graphics.Rect);
method public final boolean getClipToOutline();
+ method public final android.view.contentcapture.ContentCaptureSession getContentCaptureSession();
method public java.lang.CharSequence getContentDescription();
method public final android.content.Context getContext();
method protected android.view.ContextMenu.ContextMenuInfo getContextMenuInfo();
@@ -49884,6 +49915,7 @@ package android.view {
method public void setClickable(boolean);
method public void setClipBounds(android.graphics.Rect);
method public void setClipToOutline(boolean);
+ method public void setContentCaptureSession(android.view.contentcapture.ContentCaptureSession);
method public void setContentDescription(java.lang.CharSequence);
method public void setContextClickable(boolean);
method public void setDefaultFocusHighlightEnabled(boolean);
@@ -52144,17 +52176,56 @@ package android.view.autofill {
package android.view.contentcapture {
+ public final class ContentCaptureContext implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.view.contentcapture.ContentCaptureContext> CREATOR;
+ }
+
+ public static final class ContentCaptureContext.Builder {
+ ctor public ContentCaptureContext.Builder();
+ method public android.view.contentcapture.ContentCaptureContext build();
+ method public android.view.contentcapture.ContentCaptureContext.Builder setExtras(android.os.Bundle);
+ method public android.view.contentcapture.ContentCaptureContext.Builder setUri(android.net.Uri);
+ }
+
public final class ContentCaptureManager {
+ method public android.view.contentcapture.ContentCaptureSession createContentCaptureSession(android.view.contentcapture.ContentCaptureContext);
method public android.content.ComponentName getServiceComponentName();
method public boolean isContentCaptureEnabled();
- method public android.view.ViewStructure newVirtualViewStructure(android.view.autofill.AutofillId, int);
+ method public void removeUserData(android.view.contentcapture.UserDataRemovalRequest);
+ method public void setContentCaptureEnabled(boolean);
+ }
+
+ public final class ContentCaptureSession implements java.lang.AutoCloseable {
+ method public void close();
+ method public void destroy();
+ method public android.view.contentcapture.ContentCaptureSessionId getContentCaptureSessionId();
method public void notifyViewAppeared(android.view.ViewStructure);
method public void notifyViewDisappeared(android.view.autofill.AutofillId);
method public void notifyViewTextChanged(android.view.autofill.AutofillId, java.lang.CharSequence, int);
- method public void setContentCaptureEnabled(boolean);
field public static final int FLAG_USER_INPUT = 1; // 0x1
}
+ public final class ContentCaptureSessionId implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.view.contentcapture.ContentCaptureSessionId> CREATOR;
+ }
+
+ public final class UserDataRemovalRequest implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.view.contentcapture.UserDataRemovalRequest> CREATOR;
+ }
+
+ public static final class UserDataRemovalRequest.Builder {
+ ctor public UserDataRemovalRequest.Builder();
+ method public android.view.contentcapture.UserDataRemovalRequest.Builder addUri(android.net.Uri, boolean);
+ method public android.view.contentcapture.UserDataRemovalRequest build();
+ method public android.view.contentcapture.UserDataRemovalRequest.Builder forEverything();
+ }
+
}
package android.view.inputmethod {
@@ -56356,6 +56427,7 @@ package android.widget {
method public boolean isCursorVisible();
method public boolean isElegantTextHeight();
method public boolean isFallbackLineSpacing();
+ method public final boolean isHorizontallyScrolling();
method public boolean isInputMethodTarget();
method public boolean isSingleLine();
method public boolean isSuggestionsEnabled();
diff --git a/api/system-current.txt b/api/system-current.txt
index cc93bbafd046..a83c32def7be 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -93,6 +93,7 @@ package android {
field public static final java.lang.String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
field public static final java.lang.String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS";
field public static final java.lang.String MANAGE_ROLE_HOLDERS = "android.permission.MANAGE_ROLE_HOLDERS";
+ field public static final java.lang.String MANAGE_SENSOR_PRIVACY = "android.permission.MANAGE_SENSOR_PRIVACY";
field public static final java.lang.String MANAGE_SOUND_TRIGGER = "android.permission.MANAGE_SOUND_TRIGGER";
field public static final java.lang.String MANAGE_SUBSCRIPTION_PLANS = "android.permission.MANAGE_SUBSCRIPTION_PLANS";
field public static final java.lang.String MANAGE_USB = "android.permission.MANAGE_USB";
@@ -852,6 +853,7 @@ package android.app.role {
method public void addRoleHolderAsUser(java.lang.String, java.lang.String, android.os.UserHandle, java.util.concurrent.Executor, android.app.role.RoleManagerCallback);
method public boolean addRoleHolderFromController(java.lang.String, java.lang.String);
method public void clearRoleHoldersAsUser(java.lang.String, android.os.UserHandle, java.util.concurrent.Executor, android.app.role.RoleManagerCallback);
+ method public java.util.List<java.lang.String> getHeldRolesFromController(java.lang.String);
method public java.util.List<java.lang.String> getRoleHolders(java.lang.String);
method public java.util.List<java.lang.String> getRoleHoldersAsUser(java.lang.String, android.os.UserHandle);
method public void removeOnRoleHoldersChangedListenerAsUser(android.app.role.OnRoleHoldersChangedListener, android.os.UserHandle);
@@ -2762,7 +2764,9 @@ package android.location {
method public deprecated boolean addGpsNavigationMessageListener(android.location.GpsNavigationMessageEvent.Listener);
method public void flushGnssBatch();
method public int getGnssBatchSize();
+ method public java.lang.String getLocationControllerExtraPackage();
method public java.lang.String getNetworkProviderPackage();
+ method public boolean isLocationControllerExtraPackageEnabled();
method public boolean isLocationEnabledForUser(android.os.UserHandle);
method public boolean isProviderEnabledForUser(java.lang.String, android.os.UserHandle);
method public boolean registerGnssBatchedLocationCallback(long, boolean, android.location.BatchedLocationCallback, android.os.Handler);
@@ -2770,6 +2774,8 @@ package android.location {
method public deprecated void removeGpsNavigationMessageListener(android.location.GpsNavigationMessageEvent.Listener);
method public void requestLocationUpdates(android.location.LocationRequest, android.location.LocationListener, android.os.Looper);
method public void requestLocationUpdates(android.location.LocationRequest, android.app.PendingIntent);
+ method public void setLocationControllerExtraPackage(java.lang.String);
+ method public void setLocationControllerExtraPackageEnabled(boolean);
method public void setLocationEnabledForUser(boolean, android.os.UserHandle);
method public boolean setProviderEnabledForUser(java.lang.String, boolean, android.os.UserHandle);
method public boolean unregisterGnssBatchedLocationCallback(android.location.BatchedLocationCallback);
@@ -3884,7 +3890,11 @@ package android.net.wifi {
package android.net.wifi.aware {
public class DiscoverySession implements java.lang.AutoCloseable {
- method public android.net.NetworkSpecifier createNetworkSpecifierPmk(android.net.wifi.aware.PeerHandle, byte[]);
+ method public deprecated android.net.NetworkSpecifier createNetworkSpecifierPmk(android.net.wifi.aware.PeerHandle, byte[]);
+ }
+
+ public static class WifiAwareManager.NetworkSpecifierBuilder {
+ method public android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder setPmk(byte[]);
}
public class WifiAwareSession implements java.lang.AutoCloseable {
@@ -4528,6 +4538,18 @@ package android.provider {
field public static final java.lang.String STATE = "state";
}
+ public final class DeviceConfig {
+ method public static void addOnPropertyChangedListener(java.lang.String, java.util.concurrent.Executor, android.provider.DeviceConfig.OnPropertyChangedListener);
+ method public static java.lang.String getProperty(java.lang.String, java.lang.String);
+ method public static void removeOnPropertyChangedListener(android.provider.DeviceConfig.OnPropertyChangedListener);
+ method public static void resetToDefaults(int, java.lang.String);
+ method public static boolean setProperty(java.lang.String, java.lang.String, java.lang.String, boolean);
+ }
+
+ public static abstract interface DeviceConfig.OnPropertyChangedListener {
+ method public abstract void onPropertyChanged(java.lang.String, java.lang.String, java.lang.String);
+ }
+
public final class DocumentsContract {
method public static boolean isManageMode(android.net.Uri);
method public static android.net.Uri setManageMode(android.net.Uri);
@@ -4647,6 +4669,7 @@ package android.provider {
public final class Settings {
field public static final java.lang.String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS";
+ field public static final java.lang.String ACTION_LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS = "android.settings.LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS";
field public static final java.lang.String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS";
}
@@ -4984,34 +5007,16 @@ package android.service.contentcapture {
ctor public ContentCaptureService();
method public final java.util.Set<android.content.ComponentName> getContentCaptureDisabledActivities();
method public final java.util.Set<java.lang.String> getContentCaptureDisabledPackages();
- method public void onActivitySnapshot(android.service.contentcapture.InteractionSessionId, android.service.contentcapture.SnapshotData);
- method public abstract void onContentCaptureEventsRequest(android.service.contentcapture.InteractionSessionId, android.service.contentcapture.ContentCaptureEventsRequest);
- method public void onCreateInteractionSession(android.service.contentcapture.InteractionContext, android.service.contentcapture.InteractionSessionId);
- method public void onDestroyInteractionSession(android.service.contentcapture.InteractionSessionId);
+ method public void onActivitySnapshot(android.view.contentcapture.ContentCaptureSessionId, android.service.contentcapture.SnapshotData);
+ method public abstract void onContentCaptureEventsRequest(android.view.contentcapture.ContentCaptureSessionId, android.service.contentcapture.ContentCaptureEventsRequest);
+ method public void onCreateContentCaptureSession(android.view.contentcapture.ContentCaptureContext, android.view.contentcapture.ContentCaptureSessionId);
+ method public void onDestroyContentCaptureSession(android.view.contentcapture.ContentCaptureSessionId);
method public final void setActivityContentCaptureEnabled(android.content.ComponentName, boolean);
method public final void setContentCaptureWhitelist(java.util.List<java.lang.String>, java.util.List<android.content.ComponentName>);
method public final void setPackageContentCaptureEnabled(java.lang.String, boolean);
field public static final java.lang.String SERVICE_INTERFACE = "android.service.contentcapture.ContentCaptureService";
}
- public final class InteractionContext implements android.os.Parcelable {
- method public int describeContents();
- method public android.content.ComponentName getActivityComponent();
- method public int getDisplayId();
- method public int getFlags();
- method public int getTaskId();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.service.contentcapture.InteractionContext> CREATOR;
- field public static final int FLAG_DISABLED_BY_APP = 1; // 0x1
- field public static final int FLAG_DISABLED_BY_FLAG_SECURE = 2; // 0x2
- }
-
- public final class InteractionSessionId implements android.os.Parcelable {
- method public int describeContents();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.service.contentcapture.InteractionSessionId> CREATOR;
- }
-
public final class SnapshotData implements android.os.Parcelable {
method public int describeContents();
method public android.app.assist.AssistContent getAssistContent();
@@ -5730,16 +5735,15 @@ package android.telephony {
public abstract class NetworkService extends android.app.Service {
ctor public NetworkService();
method protected abstract android.telephony.NetworkService.NetworkServiceProvider createNetworkServiceProvider(int);
- field public static final java.lang.String NETWORK_SERVICE_EXTRA_SLOT_ID = "android.telephony.extra.SLOT_ID";
field public static final java.lang.String NETWORK_SERVICE_INTERFACE = "android.telephony.NetworkService";
}
- public class NetworkService.NetworkServiceProvider {
+ public abstract class NetworkService.NetworkServiceProvider implements java.lang.AutoCloseable {
ctor public NetworkService.NetworkServiceProvider(int);
+ method public abstract void close();
method public void getNetworkRegistrationState(int, android.telephony.NetworkServiceCallback);
method public final int getSlotId();
method public final void notifyNetworkRegistrationStateChanged();
- method protected void onDestroy();
}
public class NetworkServiceCallback {
@@ -6079,20 +6083,19 @@ package android.telephony.data {
public abstract class DataService extends android.app.Service {
ctor public DataService();
method public abstract android.telephony.data.DataService.DataServiceProvider createDataServiceProvider(int);
- field public static final java.lang.String DATA_SERVICE_EXTRA_SLOT_ID = "android.telephony.data.extra.SLOT_ID";
field public static final java.lang.String DATA_SERVICE_INTERFACE = "android.telephony.data.DataService";
field public static final int REQUEST_REASON_HANDOVER = 3; // 0x3
field public static final int REQUEST_REASON_NORMAL = 1; // 0x1
field public static final int REQUEST_REASON_SHUTDOWN = 2; // 0x2
}
- public class DataService.DataServiceProvider {
+ public abstract class DataService.DataServiceProvider implements java.lang.AutoCloseable {
ctor public DataService.DataServiceProvider(int);
+ method public abstract void close();
method public void deactivateDataCall(int, int, android.telephony.data.DataServiceCallback);
method public void getDataCallList(android.telephony.data.DataServiceCallback);
method public final int getSlotId();
method public final void notifyDataCallListChanged(java.util.List<android.telephony.data.DataCallResponse>);
- method protected void onDestroy();
method public void setDataProfile(java.util.List<android.telephony.data.DataProfile>, boolean, android.telephony.data.DataServiceCallback);
method public void setInitialAttachApn(android.telephony.data.DataProfile, boolean, android.telephony.data.DataServiceCallback);
method public void setupDataCall(int, android.telephony.data.DataProfile, boolean, boolean, int, android.net.LinkProperties, android.telephony.data.DataServiceCallback);
@@ -6614,8 +6617,8 @@ package android.telephony.ims {
field public static final int CODE_SIP_SERVER_TIMEOUT = 353; // 0x161
field public static final int CODE_SIP_SERVICE_UNAVAILABLE = 352; // 0x160
field public static final int CODE_SIP_TEMPRARILY_UNAVAILABLE = 336; // 0x150
- field public static final int CODE_SIP_TRANSACTION_DOES_NOT_EXIST = 343; // 0x157
field public static final int CODE_SIP_TOO_MANY_HOPS = 374; // 0x176
+ field public static final int CODE_SIP_TRANSACTION_DOES_NOT_EXIST = 343; // 0x157
field public static final int CODE_SIP_UNDECIPHERABLE = 378; // 0x17a
field public static final int CODE_SIP_USER_MARKED_UNWANTED = 365; // 0x16d
field public static final int CODE_SIP_USER_REJECTED = 361; // 0x169
@@ -7281,6 +7284,17 @@ package android.view.accessibility {
package android.view.contentcapture {
+ public final class ContentCaptureContext implements android.os.Parcelable {
+ method public android.content.ComponentName getActivityComponent();
+ method public int getDisplayId();
+ method public android.os.Bundle getExtras();
+ method public int getFlags();
+ method public int getTaskId();
+ method public android.net.Uri getUri();
+ field public static final int FLAG_DISABLED_BY_APP = 1; // 0x1
+ field public static final int FLAG_DISABLED_BY_FLAG_SECURE = 2; // 0x2
+ }
+
public final class ContentCaptureEvent implements android.os.Parcelable {
method public int describeContents();
method public long getEventTime();
@@ -7291,13 +7305,20 @@ package android.view.contentcapture {
method public android.view.contentcapture.ViewNode getViewNode();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.view.contentcapture.ContentCaptureEvent> CREATOR;
- field public static final deprecated int TYPE_ACTIVITY_PAUSED = 3; // 0x3
- field public static final deprecated int TYPE_ACTIVITY_RESUMED = 2; // 0x2
- field public static final deprecated int TYPE_ACTIVITY_STARTED = 1; // 0x1
- field public static final deprecated int TYPE_ACTIVITY_STOPPED = 4; // 0x4
- field public static final int TYPE_VIEW_APPEARED = 5; // 0x5
- field public static final int TYPE_VIEW_DISAPPEARED = 6; // 0x6
- field public static final int TYPE_VIEW_TEXT_CHANGED = 7; // 0x7
+ field public static final int TYPE_VIEW_APPEARED = 1; // 0x1
+ field public static final int TYPE_VIEW_DISAPPEARED = 2; // 0x2
+ field public static final int TYPE_VIEW_TEXT_CHANGED = 3; // 0x3
+ }
+
+ public final class UserDataRemovalRequest implements android.os.Parcelable {
+ method public java.lang.String getPackageName();
+ method public java.util.List<android.view.contentcapture.UserDataRemovalRequest.UriRequest> getUriRequests();
+ method public boolean isForEverything();
+ }
+
+ public final class UserDataRemovalRequest.UriRequest {
+ method public android.net.Uri getUri();
+ method public boolean isRecursive();
}
public final class ViewNode extends android.app.assist.AssistStructure.ViewNode {
diff --git a/api/test-current.txt b/api/test-current.txt
index 627ef22a5d56..d534501e1a06 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1197,7 +1197,7 @@ package android.service.notification {
field public static final java.lang.String KEY_USER_SENTIMENT = "key_user_sentiment";
}
- public abstract class ConditionProviderService extends android.app.Service {
+ public abstract deprecated class ConditionProviderService extends android.app.Service {
method public boolean isBound();
}
diff --git a/cmds/bu/src/com/android/commands/bu/Backup.java b/cmds/bu/src/com/android/commands/bu/Backup.java
index 834658da8ccc..373677eccf62 100644
--- a/cmds/bu/src/com/android/commands/bu/Backup.java
+++ b/cmds/bu/src/com/android/commands/bu/Backup.java
@@ -16,13 +16,17 @@
package com.android.commands.bu;
+import android.annotation.UserIdInt;
import android.app.backup.IBackupManager;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.system.OsConstants;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.io.IOException;
import java.util.ArrayList;
@@ -33,35 +37,50 @@ public final class Backup {
int mNextArg;
IBackupManager mBackupManager;
+ @VisibleForTesting
+ Backup(IBackupManager backupManager) {
+ mBackupManager = backupManager;
+ }
+
+ Backup() {
+ mBackupManager = IBackupManager.Stub.asInterface(ServiceManager.getService("backup"));
+ }
+
public static void main(String[] args) {
- Log.d(TAG, "Beginning: " + args[0]);
- mArgs = args;
try {
- new Backup().run();
+ new Backup().run(args);
} catch (Exception e) {
Log.e(TAG, "Error running backup/restore", e);
}
Log.d(TAG, "Finished.");
}
- public void run() {
- mBackupManager = IBackupManager.Stub.asInterface(ServiceManager.getService("backup"));
+ public void run(String[] args) {
if (mBackupManager == null) {
Log.e(TAG, "Can't obtain Backup Manager binder");
return;
}
+ Log.d(TAG, "Beginning: " + args[0]);
+ mArgs = args;
+
+ int userId = parseUserId();
+ if (!isBackupActiveForUser(userId)) {
+ Log.e(TAG, "BackupManager is not available for user " + userId);
+ return;
+ }
+
String arg = nextArg();
if (arg.equals("backup")) {
- doBackup(OsConstants.STDOUT_FILENO);
+ doBackup(OsConstants.STDOUT_FILENO, userId);
} else if (arg.equals("restore")) {
- doRestore(OsConstants.STDIN_FILENO);
+ doRestore(OsConstants.STDIN_FILENO, userId);
} else {
showUsage();
}
}
- private void doBackup(int socketFd) {
+ private void doBackup(int socketFd, @UserIdInt int userId) {
ArrayList<String> packages = new ArrayList<String>();
boolean saveApks = false;
boolean saveObbs = false;
@@ -105,6 +124,10 @@ public final class Backup {
doKeyValue = true;
} else if ("-nokeyvalue".equals(arg)) {
doKeyValue = false;
+ } else if ("-user".equals(arg)) {
+ // User ID has been processed in run(), ignore the next argument.
+ nextArg();
+ continue;
} else {
Log.w(TAG, "Unknown backup flag " + arg);
continue;
@@ -128,7 +151,7 @@ public final class Backup {
try {
fd = ParcelFileDescriptor.adoptFd(socketFd);
String[] packArray = new String[packages.size()];
- mBackupManager.adbBackup(fd, saveApks, saveObbs, saveShared, doWidgets, doEverything,
+ mBackupManager.adbBackup(userId, fd, saveApks, saveObbs, saveShared, doWidgets, doEverything,
allIncludesSystem, doCompress, doKeyValue, packages.toArray(packArray));
} catch (RemoteException e) {
Log.e(TAG, "Unable to invoke backup manager for backup");
@@ -143,12 +166,12 @@ public final class Backup {
}
}
- private void doRestore(int socketFd) {
+ private void doRestore(int socketFd, @UserIdInt int userId) {
// No arguments to restore
ParcelFileDescriptor fd = null;
try {
fd = ParcelFileDescriptor.adoptFd(socketFd);
- mBackupManager.adbRestore(fd);
+ mBackupManager.adbRestore(userId, fd);
} catch (RemoteException e) {
Log.e(TAG, "Unable to invoke backup manager for restore");
} finally {
@@ -160,11 +183,31 @@ public final class Backup {
}
}
+ private @UserIdInt int parseUserId() {
+ for (int argNumber = 0; argNumber < mArgs.length - 1; argNumber++) {
+ if ("-user".equals(mArgs[argNumber])) {
+ return UserHandle.parseUserArg(mArgs[argNumber + 1]);
+ }
+ }
+
+ return UserHandle.USER_SYSTEM;
+ }
+
+ private boolean isBackupActiveForUser(int userId) {
+ try {
+ return mBackupManager.isBackupServiceActive(userId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not access BackupManager: " + e.toString());
+ return false;
+ }
+ }
+
private static void showUsage() {
- System.err.println(" backup [-f FILE] [-apk|-noapk] [-obb|-noobb] [-shared|-noshared] [-all]");
- System.err.println(" [-system|-nosystem] [-keyvalue|-nokeyvalue] [PACKAGE...]");
+ System.err.println(" backup [-user USER_ID] [-f FILE] [-apk|-noapk] [-obb|-noobb] [-shared|-noshared]");
+ System.err.println(" [-all] [-system|-nosystem] [-keyvalue|-nokeyvalue] [PACKAGE...]");
System.err.println(" write an archive of the device's data to FILE [default=backup.adb]");
System.err.println(" package list optional if -all/-shared are supplied");
+ System.err.println(" -user: user ID for which to perform the operation (default - system user)");
System.err.println(" -apk/-noapk: do/don't back up .apk files (default -noapk)");
System.err.println(" -obb/-noobb: do/don't back up .obb files (default -noobb)");
System.err.println(" -shared|-noshared: do/don't back up shared storage (default -noshared)");
@@ -172,7 +215,8 @@ public final class Backup {
System.err.println(" -system|-nosystem: include system apps in -all (default -system)");
System.err.println(" -keyvalue|-nokeyvalue: include apps that perform key/value backups.");
System.err.println(" (default -nokeyvalue)");
- System.err.println(" restore FILE restore device contents from FILE");
+ System.err.println(" restore [-user USER_ID] FILE restore device contents from FILE");
+ System.err.println(" -user: user ID for which to perform the operation (default - system user)");
}
private String nextArg() {
diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java
index 783c8c45d603..be5a5bf6c35a 100644
--- a/cmds/sm/src/com/android/commands/sm/Sm.java
+++ b/cmds/sm/src/com/android/commands/sm/Sm.java
@@ -282,14 +282,31 @@ public final class Sm {
StorageManager.DEBUG_VIRTUAL_DISK);
}
- public void runIsolatedStorage() throws RemoteException {
- final boolean enableIsolatedStorage = Boolean.parseBoolean(nextArg());
+ public void runIsolatedStorage() {
+ final int value;
+ final int mask = StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_ON
+ | StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_OFF;
+ switch (nextArg()) {
+ case "on":
+ case "true":
+ value = StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_ON;
+ break;
+ case "off":
+ value = StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_OFF;
+ break;
+ case "default":
+ case "false":
+ value = 0;
+ break;
+ default:
+ return;
+ }
+
// Toggling isolated-storage state will result in a device reboot. So to avoid this command
// from erroring out (DeadSystemException), call setDebugFlags() in a separate thread.
new Thread(() -> {
try {
- mSm.setDebugFlags(enableIsolatedStorage ? StorageManager.DEBUG_ISOLATED_STORAGE : 0,
- StorageManager.DEBUG_ISOLATED_STORAGE);
+ mSm.setDebugFlags(value, mask);
} catch (RemoteException e) {
Log.e(TAG, "Encountered an error!", e);
}
@@ -334,7 +351,7 @@ public final class Sm {
System.err.println("");
System.err.println(" sm set-emulate-fbe [true|false]");
System.err.println("");
- System.err.println(" sm set-isolated-storage [true|false]");
+ System.err.println(" sm set-isolated-storage [on|off|default]");
System.err.println("");
return 1;
}
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 410bd198a227..5df47cd045b0 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -169,6 +169,8 @@ message Atom {
DocsUIRootVisitedReported docs_ui_root_visited = 110;
DocsUIStartupMsReported docs_ui_startup_ms = 111;
DocsUIUserActionReported docs_ui_user_action_reported = 112;
+ WifiEnabledStateChanged wifi_enabled_state_changed = 113;
+ WifiRunningStateChanged wifi_running_state_changed = 114;
}
// Pulled events will start at field 10000.
@@ -871,6 +873,37 @@ message KernelWakeupReported {
}
/**
+ * Logs when Wifi is toggled on/off.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
+ */
+message WifiEnabledStateChanged {
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 1;
+}
+
+/**
+ * Logs when an app causes Wifi to run. In this context, 'to run' means to use Wifi Client Mode.
+ * TODO: Include support for Hotspot.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
+ */
+message WifiRunningStateChanged {
+ repeated AttributionNode attribution_node = 1;
+
+ enum State {
+ OFF = 0;
+ ON = 1;
+ }
+ optional State state = 2;
+}
+
+/**
* Logs wifi locks held by an app.
*
* Logged from:
@@ -1222,7 +1255,7 @@ message UsbDeviceAttached {
STATE_CONNECTED = 1;
}
optional State state = 6;
- optional int64 last_connect_duration_ms = 7;
+ optional int64 last_connect_duration_millis = 7;
}
@@ -2627,6 +2660,8 @@ message NativeProcessMemoryState {
*
* Pulled from StatsCompanionService for all managed processes (from ActivityManagerServie)
* and for selected native processes.
+ *
+ * Pulling this atom resets high-water mark counters for all processes.
*/
message ProcessMemoryHighWaterMark {
// The uid if available. -1 means not available.
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index 14f2de0b1a48..13579d283de0 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -49,7 +49,7 @@ const int FIELD_ID_TIME_BASE = 9;
const int FIELD_ID_BUCKET_SIZE = 10;
const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11;
const int FIELD_ID_DIMENSION_PATH_IN_CONDITION = 12;
-const int FIELD_ID_IS_ACTIVE = 13;
+const int FIELD_ID_IS_ACTIVE = 14;
// for CountMetricDataWrapper
const int FIELD_ID_DATA = 1;
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index 7797bd98961d..0425671b6367 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -48,7 +48,7 @@ const int FIELD_ID_TIME_BASE = 9;
const int FIELD_ID_BUCKET_SIZE = 10;
const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11;
const int FIELD_ID_DIMENSION_PATH_IN_CONDITION = 12;
-const int FIELD_ID_IS_ACTIVE = 13;
+const int FIELD_ID_IS_ACTIVE = 14;
// for DurationMetricDataWrapper
const int FIELD_ID_DATA = 1;
// for DurationMetricData
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp
index 31a4361f353d..ea125d02f337 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp
@@ -44,7 +44,7 @@ namespace statsd {
// for StatsLogReport
const int FIELD_ID_ID = 1;
const int FIELD_ID_EVENT_METRICS = 4;
-const int FIELD_ID_IS_ACTIVE = 13;
+const int FIELD_ID_IS_ACTIVE = 14;
// for EventMetricDataWrapper
const int FIELD_ID_DATA = 1;
// for EventMetricData
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index 03e42ce76460..98a33f5280e6 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -49,7 +49,7 @@ const int FIELD_ID_TIME_BASE = 9;
const int FIELD_ID_BUCKET_SIZE = 10;
const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11;
const int FIELD_ID_DIMENSION_PATH_IN_CONDITION = 12;
-const int FIELD_ID_IS_ACTIVE = 13;
+const int FIELD_ID_IS_ACTIVE = 14;
// for GaugeMetricDataWrapper
const int FIELD_ID_DATA = 1;
const int FIELD_ID_SKIPPED = 2;
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index 1f22a6af3a3d..7475b53c0a84 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -52,7 +52,7 @@ const int FIELD_ID_TIME_BASE = 9;
const int FIELD_ID_BUCKET_SIZE = 10;
const int FIELD_ID_DIMENSION_PATH_IN_WHAT = 11;
const int FIELD_ID_DIMENSION_PATH_IN_CONDITION = 12;
-const int FIELD_ID_IS_ACTIVE = 13;
+const int FIELD_ID_IS_ACTIVE = 14;
// for ValueMetricDataWrapper
const int FIELD_ID_DATA = 1;
const int FIELD_ID_SKIPPED = 2;
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index a6f27c8aa535..5a87e46097ae 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -220,7 +220,9 @@ message StatsLogReport {
optional DimensionsValue dimensions_path_in_condition = 12;
- optional bool is_active = 13;
+ // DO NOT USE field 13.
+
+ optional bool is_active = 14;
}
message UidMapping {
diff --git a/config/dirty-image-objects b/config/dirty-image-objects
index 9b4d199dc723..9e2230b288c8 100644
--- a/config/dirty-image-objects
+++ b/config/dirty-image-objects
@@ -44,7 +44,6 @@ java.util.function.ToIntFunction
sun.misc.FormattedFloatingDecimal
java.util.stream.IntStream
android.icu.util.TimeZone
-libcore.io.DropBox
org.apache.harmony.luni.internal.util.TimezoneGetter
dalvik.system.SocketTagger
dalvik.system.CloseGuard
@@ -137,7 +136,6 @@ java.lang.CharSequence
android.icu.util.ULocale
dalvik.system.BaseDexClassLoader
android.icu.text.BreakIterator
-libcore.io.EventLogger
libcore.net.NetworkSecurityPolicy
android.icu.text.UnicodeSet
com.android.org.conscrypt.TrustedCertificateStore$PreloadHolder
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index bf1443867aff..5cd3d5d4fa4d 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -2913,7 +2913,6 @@ Lcom/android/internal/telephony/dataconnection/DcTracker;->notifyOffApnsOfAvaila
Lcom/android/internal/telephony/dataconnection/DcTracker;->onActionIntentDataStallAlarm(Landroid/content/Intent;)V
Lcom/android/internal/telephony/dataconnection/DcTracker;->onActionIntentProvisioningApnAlarm(Landroid/content/Intent;)V
Lcom/android/internal/telephony/dataconnection/DcTracker;->onRecordsLoadedOrSubIdChanged()V
-Lcom/android/internal/telephony/dataconnection/DcTracker;->onSetUserDataEnabled(Z)V
Lcom/android/internal/telephony/dataconnection/DcTracker;->onTrySetupData(Lcom/android/internal/telephony/dataconnection/ApnContext;)Z
Lcom/android/internal/telephony/dataconnection/DcTracker;->onTrySetupData(Ljava/lang/String;)Z
Lcom/android/internal/telephony/dataconnection/DcTracker;->registerSettingsObserver()V
diff --git a/config/preloaded-classes b/config/preloaded-classes
index 30959256c922..c8a2a9c19b28 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -6137,12 +6137,6 @@ libcore.io.BufferIterator
libcore.io.ClassPathURLStreamHandler
libcore.io.ClassPathURLStreamHandler$ClassPathURLConnection
libcore.io.ClassPathURLStreamHandler$ClassPathURLConnection$1
-libcore.io.DropBox
-libcore.io.DropBox$DefaultReporter
-libcore.io.DropBox$Reporter
-libcore.io.EventLogger
-libcore.io.EventLogger$DefaultReporter
-libcore.io.EventLogger$Reporter
libcore.io.ForwardingOs
libcore.io.IoBridge
libcore.io.IoTracker
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index b584d5d4c3b4..48a767bee341 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -121,7 +121,6 @@ import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillManager.AutofillClient;
import android.view.autofill.AutofillPopupWindow;
import android.view.autofill.IAutofillWindowPresenter;
-import android.view.contentcapture.ContentCaptureEvent;
import android.view.contentcapture.ContentCaptureManager;
import android.widget.AdapterView;
import android.widget.Toast;
@@ -1027,28 +1026,39 @@ public class Activity extends ContextThemeWrapper
return mContentCaptureManager;
}
- private void notifyContentCaptureManagerIfNeeded(@ContentCaptureEvent.EventType int event) {
+ /** @hide */ private static final int CONTENT_CAPTURE_START = 1;
+ /** @hide */ private static final int CONTENT_CAPTURE_FLUSH = 2;
+ /** @hide */ private static final int CONTENT_CAPTURE_STOP = 3;
+
+ /** @hide */
+ @IntDef(prefix = { "CONTENT_CAPTURE_" }, value = {
+ CONTENT_CAPTURE_START,
+ CONTENT_CAPTURE_FLUSH,
+ CONTENT_CAPTURE_STOP
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface ContentCaptureNotificationType{}
+
+
+ private void notifyContentCaptureManagerIfNeeded(@ContentCaptureNotificationType int type) {
final ContentCaptureManager cm = getContentCaptureManager();
if (cm == null || !cm.isContentCaptureEnabled()) {
return;
}
- switch (event) {
- case ContentCaptureEvent.TYPE_ACTIVITY_CREATED:
+ switch (type) {
+ case CONTENT_CAPTURE_START:
//TODO(b/111276913): decide whether the InteractionSessionId should be
- // saved / restored in the activity bundle.
- cm.onActivityCreated(mToken, getComponentName());
+ // saved / restored in the activity bundle - probably not
+ cm.onActivityStarted(mToken, getComponentName());
break;
- case ContentCaptureEvent.TYPE_ACTIVITY_DESTROYED:
- cm.onActivityDestroyed();
+ case CONTENT_CAPTURE_FLUSH:
+ cm.flush();
break;
- case ContentCaptureEvent.TYPE_ACTIVITY_STARTED:
- case ContentCaptureEvent.TYPE_ACTIVITY_RESUMED:
- case ContentCaptureEvent.TYPE_ACTIVITY_PAUSED:
- case ContentCaptureEvent.TYPE_ACTIVITY_STOPPED:
- cm.onActivityLifecycleEvent(event);
+ case CONTENT_CAPTURE_STOP:
+ cm.onActivityStopped();
break;
default:
- Log.w(TAG, "notifyContentCaptureManagerIfNeeded(): invalid type " + event);
+ Log.wtf(TAG, "Invalid @ContentCaptureNotificationType: " + type);
}
}
@@ -1417,7 +1427,6 @@ public class Activity extends ContextThemeWrapper
mRestoredFromBundle = savedInstanceState != null;
mCalled = true;
- notifyContentCaptureManagerIfNeeded(ContentCaptureEvent.TYPE_ACTIVITY_CREATED);
}
/**
@@ -1651,7 +1660,7 @@ public class Activity extends ContextThemeWrapper
if (mAutoFillResetNeeded) {
getAutofillManager().onVisibleForAutofill();
}
- notifyContentCaptureManagerIfNeeded(ContentCaptureEvent.TYPE_ACTIVITY_STARTED);
+ notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_START);
}
/**
@@ -1734,8 +1743,8 @@ public class Activity extends ContextThemeWrapper
}
}
}
- notifyContentCaptureManagerIfNeeded(ContentCaptureEvent.TYPE_ACTIVITY_RESUMED);
mCalled = true;
+ notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_FLUSH);
}
/**
@@ -2128,8 +2137,8 @@ public class Activity extends ContextThemeWrapper
mAutoFillIgnoreFirstResumePause = false;
}
}
- notifyContentCaptureManagerIfNeeded(ContentCaptureEvent.TYPE_ACTIVITY_PAUSED);
mCalled = true;
+ notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_FLUSH);
}
/**
@@ -2317,7 +2326,7 @@ public class Activity extends ContextThemeWrapper
getAutofillManager().onPendingSaveUi(AutofillManager.PENDING_UI_OPERATION_CANCEL,
mIntent.getIBinderExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN));
}
- notifyContentCaptureManagerIfNeeded(ContentCaptureEvent.TYPE_ACTIVITY_STOPPED);
+ notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_STOP);
}
}
@@ -2388,9 +2397,6 @@ public class Activity extends ContextThemeWrapper
}
dispatchActivityDestroyed();
-
- notifyContentCaptureManagerIfNeeded(ContentCaptureEvent.TYPE_ACTIVITY_DESTROYED);
-
}
/**
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index fe9b1ffb378f..1b45d172cb89 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -86,7 +86,6 @@ import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Debug;
-import android.os.DropBoxManager;
import android.os.Environment;
import android.os.FileUtils;
import android.os.GraphicsEnvironment;
@@ -166,8 +165,6 @@ import dalvik.system.CloseGuard;
import dalvik.system.VMDebug;
import dalvik.system.VMRuntime;
-import libcore.io.DropBox;
-import libcore.io.EventLogger;
import libcore.io.ForwardingOs;
import libcore.io.IoUtils;
import libcore.io.Os;
@@ -6726,9 +6723,6 @@ public final class ActivityThread extends ClientTransactionHandler {
}
}
- // add dropbox logging to libcore
- DropBox.setReporter(new DropBoxReporter());
-
ViewRootImpl.ConfigChangedCallback configChangedCallback
= (Configuration globalConfig) -> {
synchronized (mResourcesManager) {
@@ -6782,39 +6776,6 @@ public final class ActivityThread extends ClientTransactionHandler {
}
}
- private static class EventLoggingReporter implements EventLogger.Reporter {
- @Override
- public void report (int code, Object... list) {
- EventLog.writeEvent(code, list);
- }
- }
-
- private static class DropBoxReporter implements DropBox.Reporter {
-
- private DropBoxManager dropBox;
-
- public DropBoxReporter() {}
-
- @Override
- public void addData(String tag, byte[] data, int flags) {
- ensureInitialized();
- dropBox.addData(tag, data, flags);
- }
-
- @Override
- public void addText(String tag, String data) {
- ensureInitialized();
- dropBox.addText(tag, data);
- }
-
- private synchronized void ensureInitialized() {
- if (dropBox == null) {
- dropBox = currentActivityThread().getApplication()
- .getSystemService(DropBoxManager.class);
- }
- }
- }
-
private static class AndroidOs extends ForwardingOs {
/**
* Install selective syscall interception. For example, this is used to
@@ -6904,9 +6865,6 @@ public final class ActivityThread extends ClientTransactionHandler {
Environment.initForCurrentUser();
- // Set the reporter for event logging in libcore
- EventLogger.setReporter(new EventLoggingReporter());
-
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index 2c435a27cbce..680fed80d029 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -361,7 +361,8 @@ public class ActivityView extends ViewGroup {
DISPLAY_NAME + "@" + System.identityHashCode(this),
width, height, getBaseDisplayDensity(), mTmpSurface,
DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
- | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
+ | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
+ | DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL);
if (mVirtualDisplay == null) {
Log.e(TAG, "Failed to initialize ActivityView");
return;
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 2b81c86e1b0d..17529a6dacf0 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -794,12 +794,25 @@ public class ApplicationPackageManager extends PackageManager {
@Override
public List<ModuleInfo> getInstalledModules(int flags) {
- return null;
+ try {
+ return mPM.getInstalledModules(flags);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
@Override
public ModuleInfo getModuleInfo(String packageName, int flags) throws NameNotFoundException {
- return null;
+ try {
+ ModuleInfo mi = mPM.getModuleInfo(packageName, flags);
+ if (mi != null) {
+ return mi;
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ throw new NameNotFoundException("No module info for package: " + packageName);
}
@SuppressWarnings("unchecked")
@@ -3002,7 +3015,6 @@ public class ApplicationPackageManager extends PackageManager {
}
}
- @Override
public void sendDeviceCustomizationReadyBroadcast() {
try {
mPM.sendDeviceCustomizationReadyBroadcast();
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index e2f2075f0a32..fe23e21651fa 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -20,10 +20,14 @@ import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import android.app.NotificationManager.InterruptionFilter;
import android.content.ComponentName;
+import android.content.Intent;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.service.notification.ZenPolicy;
+import android.service.notification.Condition;
+
+import com.android.internal.util.Preconditions;
import java.util.Objects;
@@ -40,6 +44,7 @@ public final class AutomaticZenRule implements Parcelable {
private @InterruptionFilter int interruptionFilter;
private Uri conditionId;
private ComponentName owner;
+ private ComponentName configurationActivity;
private long creationTime;
private ZenPolicy mZenPolicy;
private boolean mModified = false;
@@ -49,39 +54,51 @@ public final class AutomaticZenRule implements Parcelable {
*
* @param name The name of the rule.
* @param owner The Condition Provider service that owns this rule.
- * @param conditionId A representation of the state that should cause the Condition Provider
- * service to apply the given interruption filter.
* @param interruptionFilter The interruption filter defines which notifications are allowed to
* interrupt the user (e.g. via sound &amp; vibration) while this rule
* is active.
* @param enabled Whether the rule is enabled.
+ * @deprecated use {@link #AutomaticZenRule(String, ComponentName, ComponentName, Uri,
+ * ZenPolicy, int, boolean)}.
*/
+ @Deprecated
public AutomaticZenRule(String name, ComponentName owner, Uri conditionId,
int interruptionFilter, boolean enabled) {
- this.name = name;
- this.owner = owner;
- this.conditionId = conditionId;
- this.interruptionFilter = interruptionFilter;
- this.enabled = enabled;
+ this(name, owner, null, conditionId, null, interruptionFilter, enabled);
}
/**
* Creates an automatic zen rule.
*
* @param name The name of the rule.
- * @param owner The Condition Provider service that owns this rule.
- * @param conditionId A representation of the state that should cause the Condition Provider
- * service to apply the given interruption filter.
+ * @param owner The Condition Provider service that owns this rule. This can be null if you're
+ * using {@link NotificationManager#setAutomaticZenRuleState(String, Condition)}
+ * instead of {@link android.service.notification.ConditionProviderService}.
+ * @param configurationActivity An activity that handles
+ * {@link NotificationManager#ACTION_AUTOMATIC_ZEN_RULE} that shows
+ * the user
+ * more information about this rule and/or allows them to
+ * configure it. This is required if you are not using a
+ * {@link android.service.notification.ConditionProviderService}.
+ * If you are, it overrides the information specified in your
+ * manifest.
+ * @param conditionId A representation of the state that should cause your app to apply the
+ * given interruption filter.
+ * @param interruptionFilter The interruption filter defines which notifications are allowed to
+ * interrupt the user (e.g. via sound &amp; vibration) while this rule
+ * is active.
* @param policy The policy defines which notifications are allowed to interrupt the user
- * while this rule is active
+ * while this rule is active. This overrides the global policy while this rule is
+ * action ({@link Condition#STATE_TRUE}).
* @param enabled Whether the rule is enabled.
*/
- public AutomaticZenRule(String name, ComponentName owner, Uri conditionId, ZenPolicy policy,
- boolean enabled) {
+ public AutomaticZenRule(String name, ComponentName owner, ComponentName configurationActivity,
+ Uri conditionId, ZenPolicy policy, int interruptionFilter, boolean enabled) {
this.name = name;
this.owner = owner;
+ this.configurationActivity = configurationActivity;
this.conditionId = conditionId;
- this.interruptionFilter = INTERRUPTION_FILTER_PRIORITY;
+ this.interruptionFilter = interruptionFilter;
this.enabled = enabled;
this.mZenPolicy = policy;
}
@@ -89,18 +106,10 @@ public final class AutomaticZenRule implements Parcelable {
/**
* @hide
*/
- public AutomaticZenRule(String name, ComponentName owner, Uri conditionId,
- int interruptionFilter, boolean enabled, long creationTime) {
- this(name, owner, conditionId, interruptionFilter, enabled);
- this.creationTime = creationTime;
- }
-
- /**
- * @hide
- */
- public AutomaticZenRule(String name, ComponentName owner, Uri conditionId, ZenPolicy policy,
- boolean enabled, long creationTime) {
- this(name, owner, conditionId, policy, enabled);
+ public AutomaticZenRule(String name, ComponentName owner, ComponentName configurationActivity,
+ Uri conditionId, ZenPolicy policy, int interruptionFilter, boolean enabled,
+ long creationTime) {
+ this(name, owner, configurationActivity, conditionId, policy, interruptionFilter, enabled);
this.creationTime = creationTime;
}
@@ -112,6 +121,7 @@ public final class AutomaticZenRule implements Parcelable {
interruptionFilter = source.readInt();
conditionId = source.readParcelable(null);
owner = source.readParcelable(null);
+ configurationActivity = source.readParcelable(null);
creationTime = source.readLong();
mZenPolicy = source.readParcelable(null);
mModified = source.readInt() == ENABLED;
@@ -125,6 +135,14 @@ public final class AutomaticZenRule implements Parcelable {
}
/**
+ * Returns the {@link ComponentName} of the activity that shows configuration options
+ * for this rule.
+ */
+ public ComponentName getConfigurationActivity() {
+ return configurationActivity;
+ }
+
+ /**
* Returns the representation of the state that causes this rule to become active.
*/
public Uri getConditionId() {
@@ -218,6 +236,15 @@ public final class AutomaticZenRule implements Parcelable {
this.mZenPolicy = zenPolicy;
}
+ /**
+ * Sets the configuration activity - an activity that handles
+ * {@link NotificationManager#ACTION_AUTOMATIC_ZEN_RULE} that shows the user more information
+ * about this rule and/or allows them to configure it.
+ */
+ public void setConfigurationActivity(ComponentName componentName) {
+ this.configurationActivity = componentName;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -235,6 +262,7 @@ public final class AutomaticZenRule implements Parcelable {
dest.writeInt(interruptionFilter);
dest.writeParcelable(conditionId, 0);
dest.writeParcelable(owner, 0);
+ dest.writeParcelable(configurationActivity, 0);
dest.writeLong(creationTime);
dest.writeParcelable(mZenPolicy, 0);
dest.writeInt(mModified ? ENABLED : DISABLED);
@@ -248,6 +276,7 @@ public final class AutomaticZenRule implements Parcelable {
.append(",interruptionFilter=").append(interruptionFilter)
.append(",conditionId=").append(conditionId)
.append(",owner=").append(owner)
+ .append(",configActivity=").append(configurationActivity)
.append(",creationTime=").append(creationTime)
.append(",mZenPolicy=").append(mZenPolicy)
.append(']').toString();
@@ -264,14 +293,15 @@ public final class AutomaticZenRule implements Parcelable {
&& other.interruptionFilter == interruptionFilter
&& Objects.equals(other.conditionId, conditionId)
&& Objects.equals(other.owner, owner)
- && other.creationTime == creationTime
- && Objects.equals(other.mZenPolicy, mZenPolicy);
+ && Objects.equals(other.mZenPolicy, mZenPolicy)
+ && Objects.equals(other.configurationActivity, configurationActivity)
+ && other.creationTime == creationTime;
}
@Override
public int hashCode() {
- return Objects.hash(enabled, name, interruptionFilter, conditionId, owner, creationTime,
- mZenPolicy, mModified);
+ return Objects.hash(enabled, name, interruptionFilter, conditionId, owner,
+ configurationActivity, mZenPolicy, mModified, creationTime);
}
public static final Parcelable.Creator<AutomaticZenRule> CREATOR
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index e508d42e0168..00567523e393 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -164,6 +164,7 @@ interface INotificationManager
boolean removeAutomaticZenRule(String id);
boolean removeAutomaticZenRules(String packageName);
int getRuleInstanceCount(in ComponentName owner);
+ void setAutomaticZenRuleState(String id, in Condition condition);
byte[] getBackupPayload(int user);
void applyRestore(in byte[] payload, int user);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 25fa897f2a56..306c366a86e4 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -41,6 +41,7 @@ import android.os.ServiceManager;
import android.os.StrictMode;
import android.os.UserHandle;
import android.provider.Settings.Global;
+import android.service.notification.Condition;
import android.service.notification.StatusBarNotification;
import android.service.notification.ZenModeConfig;
import android.util.Log;
@@ -262,6 +263,68 @@ public class NotificationManager {
@Retention(RetentionPolicy.SOURCE)
public @interface Importance {}
+ /**
+ * Activity Action: Launch an Automatic Zen Rule configuration screen
+ * <p>
+ * Input: Optionally, {@link #EXTRA_AUTOMATIC_RULE_ID}, if the configuration screen for an
+ * existing rule should be displayed. If the rule id is missing or null, apps should display
+ * a configuration screen where users can create a new instance of the rule.
+ * <p>
+ * Output: Nothing
+ * <p>
+ * You can have multiple activities handling this intent, if you support multiple
+ * {@link AutomaticZenRule rules}. In order for the system to properly display all of your
+ * rule types so that users can create new instances or configure existing ones, you need
+ * to add some extra metadata ({@link #META_DATA_AUTOMATIC_RULE_TYPE})
+ * to your activity tag in your manifest. If you'd like to limit the number of rules a user
+ * can create from this flow, you can additionally optionally include
+ * {@link #META_DATA_RULE_INSTANCE_LIMIT}.
+ *
+ * For example,
+ * &lt;meta-data
+ * android:name="android.app.zen.automatic.ruleType"
+ * android:value="@string/my_condition_rule">
+ * &lt;/meta-data>
+ * &lt;meta-data
+ * android:name="android.app.zen.automatic.ruleInstanceLimit"
+ * android:value="1">
+ * &lt;/meta-data>
+ * </p>
+ * </p>
+ *
+ * @see {@link #addAutomaticZenRule(AutomaticZenRule)}
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_AUTOMATIC_ZEN_RULE =
+ "android.app.action.AUTOMATIC_ZEN_RULE";
+
+ /**
+ * Used as an optional string extra on {@link #ACTION_AUTOMATIC_ZEN_RULE} intents. If
+ * provided, contains the id of the {@link AutomaticZenRule} (as returned from
+ * {@link NotificationManager#addAutomaticZenRule(AutomaticZenRule)}) for which configuration
+ * settings should be displayed.
+ */
+ public static final String EXTRA_AUTOMATIC_RULE_ID = "android.app.extra.AUTOMATIC_RULE_ID";
+
+ /**
+ * A required {@code meta-data} tag for activities that handle
+ * {@link #ACTION_AUTOMATIC_ZEN_RULE}.
+ *
+ * This tag should contain a localized name of the type of the zen rule provided by the
+ * activity.
+ */
+ public static final String META_DATA_AUTOMATIC_RULE_TYPE = "android.app.automatic.ruleType";
+
+ /**
+ * An optional {@code meta-data} tag for activities that handle
+ * {@link #ACTION_AUTOMATIC_ZEN_RULE}.
+ *
+ * This tag should contain the maximum number of rule instances that
+ * can be created for this rule type. Omit or enter a value <= 0 to allow unlimited instances.
+ */
+ public static final String META_DATA_RULE_INSTANCE_LIMIT =
+ "android.app.zen.automatic.ruleInstanceLimit";
+
/** Value signifying that the user has not expressed a per-app visibility override value.
* @hide */
public static final int VISIBILITY_NO_OVERRIDE = -1000;
@@ -859,14 +922,10 @@ public class NotificationManager {
List<ZenModeConfig.ZenRule> rules = service.getZenRules();
Map<String, AutomaticZenRule> ruleMap = new HashMap<>();
for (ZenModeConfig.ZenRule rule : rules) {
- if (rule.zenPolicy == null) {
- ruleMap.put(rule.id, new AutomaticZenRule(rule.name, rule.component,
- rule.conditionId, zenModeToInterruptionFilter(rule.zenMode),
- rule.enabled, rule.creationTime));
- } else {
- ruleMap.put(rule.id, new AutomaticZenRule(rule.name, rule.component,
- rule.conditionId, rule.zenPolicy, rule.enabled, rule.creationTime));
- }
+ ruleMap.put(rule.id, new AutomaticZenRule(rule.name, rule.component,
+ rule.configurationActivity, rule.conditionId, rule.zenPolicy,
+ zenModeToInterruptionFilter(rule.zenMode), rule.enabled,
+ rule.creationTime));
}
return ruleMap;
} catch (RemoteException e) {
@@ -936,6 +995,26 @@ public class NotificationManager {
}
/**
+ * Informs the notification manager that the state of an {@link AutomaticZenRule} has changed.
+ * Use this method to put the system into Do Not Disturb mode or request that it exits Do Not
+ * Disturb mode. The calling app must own the provided {@link android.app.AutomaticZenRule}.
+ * <p>
+ * This method can be used in conjunction with or as a replacement to
+ * {@link android.service.notification.ConditionProviderService#notifyCondition(Condition)}.
+ * </p>
+ * @param id The id of the rule whose state should change
+ * @param condition The new state of this rule
+ */
+ public void setAutomaticZenRuleState(String id, Condition condition) {
+ INotificationManager service = getService();
+ try {
+ service.setAutomaticZenRuleState(id, condition);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Deletes the automatic zen rule with the given id.
*
* <p>
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index f4fd5d132069..45e87e045ae2 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -54,6 +54,7 @@ import android.debug.IAdbManager;
import android.hardware.ConsumerIrManager;
import android.hardware.ISerialManager;
import android.hardware.SensorManager;
+import android.hardware.SensorPrivacyManager;
import android.hardware.SerialManager;
import android.hardware.SystemSensorManager;
import android.hardware.biometrics.BiometricManager;
@@ -503,6 +504,13 @@ final class SystemServiceRegistry {
ctx.mMainThread.getHandler().getLooper());
}});
+ registerService(Context.SENSOR_PRIVACY_SERVICE, SensorPrivacyManager.class,
+ new CachedServiceFetcher<SensorPrivacyManager>() {
+ @Override
+ public SensorPrivacyManager createService(ContextImpl ctx) {
+ return SensorPrivacyManager.getInstance(ctx);
+ }});
+
registerService(Context.STATS_MANAGER, StatsManager.class,
new CachedServiceFetcher<StatsManager>() {
@Override
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 0afb98f1ed84..f1e6b06e8bac 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -189,8 +189,11 @@ interface IBackupManager {
* completed.
*
* <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ * If the {@code userId} is different from the calling user id, then the caller must hold the
+ * android.permission.INTERACT_ACROSS_USERS_FULL permission.
*
- * @param fd The file descriptor to which a 'tar' file stream is to be written
+ * @param userId User id for which backup should be performed.
+ * @param fd The file descriptor to which a 'tar' file stream is to be written.
* @param includeApks If <code>true</code>, the resulting tar stream will include the
* application .apk files themselves as well as their data.
* @param includeObbs If <code>true</code>, the resulting tar stream will include any
@@ -209,7 +212,7 @@ interface IBackupManager {
* @param packageNames The package names of the apps whose data (and optionally .apk files)
* are to be backed up. The <code>allApps</code> parameter supersedes this.
*/
- void adbBackup(in ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
+ void adbBackup(int userId, in ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
boolean includeShared, boolean doWidgets, boolean allApps, boolean allIncludesSystem,
boolean doCompress, boolean doKeyValue, in String[] packageNames);
@@ -227,8 +230,12 @@ interface IBackupManager {
* Currently only used by the 'adb restore' command.
*
* <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ * If the {@code userId} is different from the calling user id, then the caller must hold the
+ * android.permission.INTERACT_ACROSS_USERS_FULL.
+ *
+ * @param userId User id for which restore should be performed.
*/
- void adbRestore(in ParcelFileDescriptor fd);
+ void adbRestore(int userId, in ParcelFileDescriptor fd);
/**
* Confirm that the requested full backup/restore operation can proceed. The system will
diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java
index e84517dc6942..e7fe16123036 100644
--- a/core/java/android/app/job/JobInfo.java
+++ b/core/java/android/app/job/JobInfo.java
@@ -1545,13 +1545,6 @@ public class JobInfo implements Parcelable {
* @return The job object to hand to the JobScheduler. This object is immutable.
*/
public JobInfo build() {
- // Allow jobs with no constraints - What am I, a database?
- if (!mHasEarlyConstraint && !mHasLateConstraint && mConstraintFlags == 0 &&
- mNetworkRequest == null &&
- mTriggerContentUris == null) {
- throw new IllegalArgumentException("You're trying to build a job with no " +
- "constraints, this is not allowed.");
- }
// Check that network estimates require network type
if ((mNetworkDownloadBytes > 0 || mNetworkUploadBytes > 0) && mNetworkRequest == null) {
throw new IllegalArgumentException(
diff --git a/core/java/android/app/role/IRoleManager.aidl b/core/java/android/app/role/IRoleManager.aidl
index 4ce0f318bad5..0c9b41bf8d68 100644
--- a/core/java/android/app/role/IRoleManager.aidl
+++ b/core/java/android/app/role/IRoleManager.aidl
@@ -48,4 +48,6 @@ interface IRoleManager {
boolean addRoleHolderFromController(in String roleName, in String packageName);
boolean removeRoleHolderFromController(in String roleName, in String packageName);
+
+ List<String> getHeldRolesFromController(in String packageName);
}
diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java
index 5d101ab479ac..2d630a61a1c3 100644
--- a/core/java/android/app/role/RoleManager.java
+++ b/core/java/android/app/role/RoleManager.java
@@ -542,6 +542,27 @@ public final class RoleManager {
}
}
+
+ /**
+ * Returns the list of all roles that the given package is currently holding
+ *
+ * @param packageName the package name
+ * @return the list of role names
+ *
+ * @hide
+ */
+ @RequiresPermission(PERMISSION_MANAGE_ROLES_FROM_CONTROLLER)
+ @SystemApi
+ @NonNull
+ public List<String> getHeldRolesFromController(@NonNull String packageName) {
+ Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
+ try {
+ return mService.getHeldRolesFromController(packageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private static class RoleManagerCallbackDelegate extends IRoleManagerCallback.Stub {
@NonNull
diff --git a/core/java/android/content/ComponentName.java b/core/java/android/content/ComponentName.java
index 54e6342747db..e6ffe8b4fe86 100644
--- a/core/java/android/content/ComponentName.java
+++ b/core/java/android/content/ComponentName.java
@@ -192,6 +192,17 @@ public final class ComponentName implements Parcelable, Cloneable, Comparable<Co
}
/**
+ * Helper to get {@link #flattenToShortString()} in a {@link ComponentName} reference that can
+ * be {@code null}.
+ *
+ * @hide
+ */
+ @Nullable
+ public static String flattenToShortString(@Nullable ComponentName componentName) {
+ return componentName == null ? null : componentName.flattenToShortString();
+ }
+
+ /**
* Return a String that unambiguously describes both the package and
* class names contained in the ComponentName. You can later recover
* the ComponentName from this string through
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index b39010d9d14e..001e328fb0ba 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3080,6 +3080,7 @@ public abstract class Context {
//@hide: COUNTRY_DETECTOR,
SEARCH_SERVICE,
SENSOR_SERVICE,
+ SENSOR_PRIVACY_SERVICE,
STORAGE_SERVICE,
STORAGE_STATS_SERVICE,
WALLPAPER_SERVICE,
@@ -3544,6 +3545,18 @@ public abstract class Context {
/**
* Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.hardware.SensorPrivacyManager} for accessing sensor privacy
+ * functions.
+ *
+ * @see #getSystemService(String)
+ * @see android.hardware.SensorPrivacyManager
+ *
+ * @hide
+ */
+ public static final String SENSOR_PRIVACY_SERVICE = "sensor_privacy";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
* android.os.storage.StorageManager} for accessing system storage
* functions.
*
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index eea2b8873fe7..a4ea513a055f 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -36,6 +36,7 @@ import android.content.pm.IOnPermissionsChangeListener;
import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.InstrumentationInfo;
import android.content.pm.KeySet;
+import android.content.pm.ModuleInfo;
import android.content.pm.PackageInfo;
import android.content.pm.ParceledListSlice;
import android.content.pm.ProviderInfo;
@@ -680,4 +681,8 @@ interface IPackageManager {
boolean isPackageStateProtected(String packageName, int userId);
void sendDeviceCustomizationReadyBroadcast();
+
+ List<ModuleInfo> getInstalledModules(int flags);
+
+ ModuleInfo getModuleInfo(String packageName, int flags);
}
diff --git a/core/java/android/content/pm/ModuleInfo.aidl b/core/java/android/content/pm/ModuleInfo.aidl
new file mode 100644
index 000000000000..cc13bf1044bc
--- /dev/null
+++ b/core/java/android/content/pm/ModuleInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+parcelable ModuleInfo;
diff --git a/core/java/android/hardware/ISensorPrivacyListener.aidl b/core/java/android/hardware/ISensorPrivacyListener.aidl
new file mode 100644
index 000000000000..5d4026541c4c
--- /dev/null
+++ b/core/java/android/hardware/ISensorPrivacyListener.aidl
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware;
+
+/**
+ * @hide
+ */
+oneway interface ISensorPrivacyListener {
+ // Since these transactions are also called from native code, these must be kept in sync with
+ // the ones in
+ // frameworks/native/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyListener.aidl
+ // =============== Beginning of transactions used on native side as well ======================
+ void onSensorPrivacyChanged(boolean enabled);
+ // =============== End of transactions used on native side as well ============================
+}
diff --git a/core/java/android/hardware/ISensorPrivacyManager.aidl b/core/java/android/hardware/ISensorPrivacyManager.aidl
new file mode 100644
index 000000000000..1ba7b989a764
--- /dev/null
+++ b/core/java/android/hardware/ISensorPrivacyManager.aidl
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware;
+
+import android.hardware.ISensorPrivacyListener;
+
+/** @hide */
+interface ISensorPrivacyManager {
+ // Since these transactions are also called from native code, these must be kept in sync with
+ // the ones in
+ // frameworks/native/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl
+ // =============== Beginning of transactions used on native side as well ======================
+ void addSensorPrivacyListener(in ISensorPrivacyListener listener);
+
+ void removeSensorPrivacyListener(in ISensorPrivacyListener listener);
+
+ boolean isSensorPrivacyEnabled();
+
+ void setSensorPrivacy(boolean enable);
+ // =============== End of transactions used on native side as well ============================
+} \ No newline at end of file
diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java
new file mode 100644
index 000000000000..274202f7c190
--- /dev/null
+++ b/core/java/android/hardware/SensorPrivacyManager.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * This class provides access to the sensor privacy services; sensor privacy allows the
+ * user to disable access to all sensors on the device. This class provides methods to query the
+ * current state of sensor privacy as well as to register / unregister for notification when
+ * the sensor privacy state changes.
+ *
+ * @hide
+ */
+@SystemService(Context.SENSOR_PRIVACY_SERVICE)
+public final class SensorPrivacyManager {
+
+ /**
+ * A class implementing this interface can register with the {@link
+ * android.hardware.SensorPrivacyManager} to receive notification when the sensor privacy
+ * state changes.
+ */
+ public interface OnSensorPrivacyChangedListener {
+ /**
+ * Callback invoked when the sensor privacy state changes.
+ *
+ * @param enabled true if sensor privacy is enabled, false otherwise.
+ */
+ void onSensorPrivacyChanged(boolean enabled);
+ }
+
+ private static final Object sInstanceLock = new Object();
+
+ @GuardedBy("sInstanceLock")
+ private static SensorPrivacyManager sInstance;
+
+ @NonNull
+ private final Context mContext;
+
+ @NonNull
+ private final ISensorPrivacyManager mService;
+
+ @NonNull
+ private final ArrayMap<OnSensorPrivacyChangedListener, ISensorPrivacyListener> mListeners;
+
+ /**
+ * Private constructor to ensure only a single instance is created.
+ */
+ private SensorPrivacyManager(Context context, ISensorPrivacyManager service) {
+ mContext = context;
+ mService = service;
+ mListeners = new ArrayMap<>();
+ }
+
+ /**
+ * Returns the single instance of the SensorPrivacyManager.
+ */
+ public static SensorPrivacyManager getInstance(Context context) {
+ synchronized (sInstanceLock) {
+ if (sInstance == null) {
+ try {
+ IBinder b = ServiceManager.getServiceOrThrow(Context.SENSOR_PRIVACY_SERVICE);
+ ISensorPrivacyManager service = ISensorPrivacyManager.Stub.asInterface(b);
+ sInstance = new SensorPrivacyManager(context, service);
+ } catch (ServiceManager.ServiceNotFoundException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ return sInstance;
+ }
+ }
+
+ /**
+ * Sets sensor privacy to the specified state.
+ *
+ * @param enable the state to which sensor privacy should be set.
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY)
+ public void setSensorPrivacy(boolean enable) {
+ try {
+ mService.setSensorPrivacy(enable);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Registers a new listener to receive notification when the state of sensor privacy
+ * changes.
+ *
+ * @param listener the OnSensorPrivacyChangedListener to be notified when the state of sensor
+ * privacy changes.
+ */
+ public void addSensorPrivacyListener(final OnSensorPrivacyChangedListener listener) {
+ synchronized (mListeners) {
+ ISensorPrivacyListener iListener = mListeners.get(listener);
+ if (iListener == null) {
+ iListener = new ISensorPrivacyListener.Stub() {
+ @Override
+ public void onSensorPrivacyChanged(boolean enabled) {
+ listener.onSensorPrivacyChanged(enabled);
+ }
+ };
+ mListeners.put(listener, iListener);
+ }
+
+ try {
+ mService.addSensorPrivacyListener(iListener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Unregisters the specified listener from receiving notifications when the state of sensor
+ * privacy changes.
+ *
+ * @param listener the OnSensorPrivacyChangedListener to be unregistered from notifications when
+ * sensor privacy changes.
+ */
+ public void removeSensorPrivacyListener(OnSensorPrivacyChangedListener listener) {
+ synchronized (mListeners) {
+ ISensorPrivacyListener iListener = mListeners.get(listener);
+ if (iListener != null) {
+ mListeners.remove(iListener);
+ try {
+ mService.removeSensorPrivacyListener(iListener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns whether sensor privacy is currently enabled.
+ *
+ * @return true if sensor privacy is currently enabled, false otherwise.
+ */
+ public boolean isSensorPrivacyEnabled() {
+ try {
+ return mService.isSensorPrivacyEnabled();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/core/java/android/inputmethodservice/Keyboard.java b/core/java/android/inputmethodservice/Keyboard.java
index 51d33b2f1d4a..3f25113874e8 100644
--- a/core/java/android/inputmethodservice/Keyboard.java
+++ b/core/java/android/inputmethodservice/Keyboard.java
@@ -16,8 +16,6 @@
package android.inputmethodservice;
-import org.xmlpull.v1.XmlPullParserException;
-
import android.annotation.UnsupportedAppUsage;
import android.annotation.XmlRes;
import android.content.Context;
@@ -27,10 +25,12 @@ import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.text.TextUtils;
+import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.util.Xml;
-import android.util.DisplayMetrics;
+
+import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
@@ -59,7 +59,12 @@ import java.util.StringTokenizer;
* @attr ref android.R.styleable#Keyboard_keyHeight
* @attr ref android.R.styleable#Keyboard_horizontalGap
* @attr ref android.R.styleable#Keyboard_verticalGap
+ * @deprecated This class is deprecated because this is just a convenient UI widget class that
+ * application developers can re-implement on top of existing public APIs. If you have
+ * already depended on this class, consider copying the implementation from AOSP into
+ * your project or re-implementing a similar widget by yourselves
*/
+@Deprecated
public class Keyboard {
static final String TAG = "Keyboard";
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index 9ca804975b7a..45f067b95298 100644
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -65,7 +65,13 @@ import java.util.Map;
* @attr ref android.R.styleable#KeyboardView_keyTextColor
* @attr ref android.R.styleable#KeyboardView_verticalCorrection
* @attr ref android.R.styleable#KeyboardView_popupLayout
+ *
+ * @deprecated This class is deprecated because this is just a convenient UI widget class that
+ * application developers can re-implement on top of existing public APIs. If you have
+ * already depended on this class, consider copying the implementation from AOSP into
+ * your project or re-implementing a similar widget by yourselves
*/
+@Deprecated
public class KeyboardView extends View implements View.OnClickListener {
/**
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 567efa7b7afb..428d9e156e37 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -49,6 +49,7 @@ import android.util.Slog;
import android.webkit.MimeTypeMap;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.SizedInputStream;
import libcore.io.IoUtils;
@@ -110,8 +111,6 @@ public class FileUtils {
public static final Pattern SAFE_FILENAME_PATTERN = Pattern.compile("[\\w%+,./=_-]+");
}
- private static final File[] EMPTY = new File[0];
-
// non-final so it can be toggled by Robolectric's ShadowFileUtils
private static boolean sEnableCopyOptimizations = true;
@@ -1164,35 +1163,20 @@ public class FileUtils {
/** {@hide} */
public static @NonNull String[] listOrEmpty(@Nullable File dir) {
- if (dir == null) return EmptyArray.STRING;
- final String[] res = dir.list();
- if (res != null) {
- return res;
- } else {
- return EmptyArray.STRING;
- }
+ return (dir != null) ? ArrayUtils.defeatNullable(dir.list())
+ : EmptyArray.STRING;
}
/** {@hide} */
public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) {
- if (dir == null) return EMPTY;
- final File[] res = dir.listFiles();
- if (res != null) {
- return res;
- } else {
- return EMPTY;
- }
+ return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles())
+ : ArrayUtils.EMPTY_FILE;
}
/** {@hide} */
public static @NonNull File[] listFilesOrEmpty(@Nullable File dir, FilenameFilter filter) {
- if (dir == null) return EMPTY;
- final File[] res = dir.listFiles(filter);
- if (res != null) {
- return res;
- } else {
- return EMPTY;
- }
+ return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles(filter))
+ : ArrayUtils.EMPTY_FILE;
}
/** {@hide} */
diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java
index bdc776dd3702..cbcf600f0fb8 100644
--- a/core/java/android/os/SystemProperties.java
+++ b/core/java/android/os/SystemProperties.java
@@ -25,10 +25,15 @@ import android.util.MutableInt;
import com.android.internal.annotations.GuardedBy;
+import libcore.util.HexEncoding;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
-
/**
* Gives access to the system properties store. The system properties
* store contains a list of string key-value pairs.
@@ -232,6 +237,27 @@ public class SystemProperties {
native_report_sysprop_change();
}
+ /**
+ * Return a {@code SHA-1} digest of the given keys and their values as a
+ * hex-encoded string. The ordering of the incoming keys doesn't change the
+ * digest result.
+ *
+ * @hide
+ */
+ public static @NonNull String digestOf(@NonNull String... keys) {
+ Arrays.sort(keys);
+ try {
+ final MessageDigest digest = MessageDigest.getInstance("SHA-1");
+ for (String key : keys) {
+ final String item = key + "=" + get(key) + "\n";
+ digest.update(item.getBytes(StandardCharsets.UTF_8));
+ }
+ return HexEncoding.encodeToString(digest.digest()).toLowerCase();
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
private SystemProperties() {
}
}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index d315383a5775..8b1d3c6c839f 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -226,7 +226,9 @@ public class StorageManager {
/** {@hide} */
public static final int DEBUG_VIRTUAL_DISK = 1 << 5;
/** {@hide} */
- public static final int DEBUG_ISOLATED_STORAGE = 1 << 6;
+ public static final int DEBUG_ISOLATED_STORAGE_FORCE_ON = 1 << 6;
+ /** {@hide} */
+ public static final int DEBUG_ISOLATED_STORAGE_FORCE_OFF = 1 << 7;
/** {@hide} */
public static final int FLAG_STORAGE_DE = IInstalld.FLAG_STORAGE_DE;
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
new file mode 100644
index 000000000000..4e207ed6556e
--- /dev/null
+++ b/core/java/android/provider/DeviceConfig.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.ActivityThread;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.provider.Settings.ResetMode;
+import android.util.Pair;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * Device level configuration parameters which can be tuned by a separate configuration service.
+ *
+ * @hide
+ */
+@SystemApi
+public final class DeviceConfig {
+ /**
+ * The content:// style URL for the config table.
+ *
+ * @hide
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://" + Settings.AUTHORITY + "/config");
+
+ private static final Object sLock = new Object();
+ @GuardedBy("sLock")
+ private static Map<OnPropertyChangedListener, Pair<String, Executor>> sListeners =
+ new HashMap<>();
+ @GuardedBy("sLock")
+ private static Map<String, Pair<ContentObserver, Integer>> sNamespaces = new HashMap<>();
+
+ // Should never be invoked
+ private DeviceConfig() {
+ }
+
+ /**
+ * Look up the value of a property for a particular namespace.
+ *
+ * @param namespace The namespace containing the property to look up.
+ * @param name The name of the property to look up.
+ * @return the corresponding value, or null if not present.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static String getProperty(String namespace, String name) {
+ ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
+ String compositeName = createCompositeName(namespace, name);
+ return Settings.Config.getString(contentResolver, compositeName);
+ }
+
+ /**
+ * Create a new property with the the provided name and value in the provided namespace, or
+ * update the value of such a property if it already exists. The same name can exist in multiple
+ * namespaces and might have different values in any or all namespaces.
+ * <p>
+ * The method takes an argument indicating whether to make the value the default for this
+ * property.
+ * <p>
+ * All properties stored for a particular scope can be reverted to their default values
+ * by passing the namespace to {@link #resetToDefaults(int, String)}.
+ *
+ * @param namespace The namespace containing the property to create or update.
+ * @param name The name of the property to create or update.
+ * @param value The value to store for the property.
+ * @param makeDefault Whether to make the new value the default one.
+ * @return True if the value was set, false if the storage implementation throws errors.
+ * @see #resetToDefaults(int, String).
+ *
+ * @hide
+ */
+ @SystemApi
+ public static boolean setProperty(
+ String namespace, String name, String value, boolean makeDefault) {
+ ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
+ String compositeName = createCompositeName(namespace, name);
+ return Settings.Config.putString(contentResolver, compositeName, value, makeDefault);
+ }
+
+ /**
+ * Reset properties to their default values.
+ * <p>
+ * The method accepts an optional namespace parameter. If provided, only properties set within
+ * that namespace will be reset. Otherwise, all properties will be reset.
+ *
+ * @param resetMode The reset mode to use.
+ * @param namespace Optionally, the specific namespace which resets will be limited to.
+ * @see #setProperty(String, String, String, boolean)
+ *
+ * @hide
+ */
+ @SystemApi
+ public static void resetToDefaults(@ResetMode int resetMode, @Nullable String namespace) {
+ ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
+ Settings.Config.resetToDefaults(contentResolver, resetMode, namespace);
+ }
+
+ /**
+ * Add a listener for property changes.
+ * <p>
+ * This listener will be called whenever properties in the specified namespace change. Callbacks
+ * will be made on the specified executor. Future calls to this method with the same listener
+ * will replace the old namespace and executor. Remove the listener entirely by calling
+ * {@link #removeOnPropertyChangedListener(OnPropertyChangedListener)}.
+ *
+ * @param namespace The namespace containing properties to monitor.
+ * @param executor The executor which will be used to run callbacks.
+ * @param onPropertyChangedListener The listener to add.
+ * @see #removeOnPropertyChangedListener(OnPropertyChangedListener)
+ *
+ * @hide
+ */
+ @SystemApi
+ public static void addOnPropertyChangedListener(
+ @NonNull String namespace,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnPropertyChangedListener onPropertyChangedListener) {
+ synchronized (sLock) {
+ Pair<String, Executor> oldNamespace = sListeners.get(onPropertyChangedListener);
+ if (oldNamespace == null) {
+ // Brand new listener, add it to the list.
+ sListeners.put(onPropertyChangedListener, new Pair<>(namespace, executor));
+ incrementNamespace(namespace);
+ } else if (namespace.equals(oldNamespace.first)) {
+ // Listener is already registered for this namespace, update executor just in case.
+ sListeners.put(onPropertyChangedListener, new Pair<>(namespace, executor));
+ } else {
+ // Update this listener from an old namespace to the new one.
+ decrementNamespace(sListeners.get(onPropertyChangedListener).first);
+ sListeners.put(onPropertyChangedListener, new Pair<>(namespace, executor));
+ incrementNamespace(namespace);
+ }
+ }
+ }
+
+ /**
+ * Remove a listener for property changes. The listener will receive no further notification of
+ * property changes.
+ *
+ * @param onPropertyChangedListener The listener to remove.
+ * @see #addOnPropertyChangedListener(String, Executor, OnPropertyChangedListener)
+ *
+ * @hide
+ */
+ @SystemApi
+ public static void removeOnPropertyChangedListener(
+ OnPropertyChangedListener onPropertyChangedListener) {
+ synchronized (sLock) {
+ if (sListeners.containsKey(onPropertyChangedListener)) {
+ decrementNamespace(sListeners.get(onPropertyChangedListener).first);
+ sListeners.remove(onPropertyChangedListener);
+ }
+ }
+ }
+
+ private static String createCompositeName(String namespace, String name) {
+ return namespace + "/" + name;
+ }
+
+ private static Uri createNamespaceUri(String namespace) {
+ return CONTENT_URI.buildUpon().appendPath(namespace).build();
+ }
+
+ /**
+ * Increment the count used to represent the number of listeners subscribed to the given
+ * namespace. If this is the first (i.e. incrementing from 0 to 1) for the given namespace, a
+ * ContentObserver is registered.
+ *
+ * @param namespace The namespace to increment the count for.
+ */
+ @GuardedBy("sLock")
+ private static void incrementNamespace(String namespace) {
+ Pair<ContentObserver, Integer> namespaceCount = sNamespaces.get(namespace);
+ if (namespaceCount != null) {
+ sNamespaces.put(namespace, new Pair<>(namespaceCount.first, namespaceCount.second + 1));
+ } else {
+ // This is a new namespace, register a ContentObserver for it.
+ ContentObserver contentObserver = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ handleChange(uri);
+ }
+ };
+ ActivityThread.currentApplication().getContentResolver()
+ .registerContentObserver(createNamespaceUri(namespace), true, contentObserver);
+ sNamespaces.put(namespace, new Pair<>(contentObserver, 1));
+ }
+ }
+
+ /**
+ * Decrement the count used to represent th enumber of listeners subscribed to the given
+ * namespace. If this is the final decrement call (i.e. decrementing from 1 to 0) for the given
+ * namespace, the ContentObserver that had been tracking it will be removed.
+ *
+ * @param namespace The namespace to decrement the count for.
+ */
+ @GuardedBy("sLock")
+ private static void decrementNamespace(String namespace) {
+ Pair<ContentObserver, Integer> namespaceCount = sNamespaces.get(namespace);
+ if (namespaceCount == null) {
+ // This namespace is not registered and does not need to be decremented
+ return;
+ } else if (namespaceCount.second > 1) {
+ sNamespaces.put(namespace, new Pair<>(namespaceCount.first, namespaceCount.second - 1));
+ } else {
+ // Decrementing a namespace to zero means we no longer need its ContentObserver.
+ ActivityThread.currentApplication().getContentResolver()
+ .unregisterContentObserver(namespaceCount.first);
+ sNamespaces.remove(namespace);
+ }
+ }
+
+ private static void handleChange(Uri uri) {
+ List<String> pathSegments = uri.getPathSegments();
+ // pathSegments(0) is "config"
+ String namespace = pathSegments.get(1);
+ String name = pathSegments.get(2);
+ String value = getProperty(namespace, name);
+ synchronized (sLock) {
+ for (OnPropertyChangedListener listener : sListeners.keySet()) {
+ if (namespace.equals(sListeners.get(listener).first)) {
+ sListeners.get(listener).second.execute(new Runnable() {
+ @Override
+ public void run() {
+ listener.onPropertyChanged(namespace, name, value);
+ }
+
+ });
+ }
+ }
+ }
+ }
+
+ /**
+ * Interface for monitoring to properties.
+ * <p>
+ * Override {@link #onPropertyChanged(String, String, String)} to handle callbacks for changes.
+ */
+ public interface OnPropertyChangedListener {
+ /**
+ * Called when a property has changed.
+ *
+ * @param namespace The namespace containing the property which has changed.
+ * @param name The name of the property which has changed.
+ * @param value The new value of the property which has changed.
+ */
+ void onPropertyChanged(String namespace, String name, String value);
+ }
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index cbcc492f1749..299db730b3c6 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -150,6 +150,23 @@ public final class Settings {
"android.settings.LOCATION_SOURCE_SETTINGS";
/**
+ * Activity Action: Show settings to allow configuration of location controller extra package.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS =
+ "android.settings.LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS";
+
+ /**
* Activity Action: Show scanning settings to allow configuration of Wi-Fi
* and Bluetooth scanning settings.
* <p>
@@ -8262,6 +8279,38 @@ public final class Settings {
"packages_to_clear_data_before_full_restore";
/**
+ * Indicates the location state should be maintained after sensor privacy is disabled.
+ * @hide
+ */
+ public static final String MAINTAIN_LOCATION_AFTER_SP_DISABLED = "0";
+
+ /**
+ * Indicates location should be reenabled after sensor privacy is disabled.
+ * @hide
+ */
+ public static final String REENABLE_LOCATION_AFTER_SP_DISABLED = "1";
+
+ /**
+ * Indicates the state of airplane mode should be maintained after sensor privacy is
+ * disabled.
+ * @hide
+ */
+ public static final String MAINTAIN_AIRPLANE_MODE_AFTER_SP_DISABLED = "0";
+
+ /**
+ * Indicates airplane mode should be disabled after sensor privacy is disabled.
+ * @hide
+ */
+ public static final String DISABLE_AIRPLANE_MODE_AFTER_SP_DISABLED = "1";
+
+ /**
+ * The state of all sensors managed by SensorPrivacyService when sensor privacy is enabled.
+ * @hide
+ */
+ public static final String SENSOR_PRIVACY_SENSOR_STATE =
+ "sensor_privacy_sensor_state";
+
+ /**
* Setting to determine whether to use the new notification priority handling features.
* @hide
*/
@@ -12915,6 +12964,11 @@ public final class Settings {
public static final String CONTENT_CAPTURE_SERVICE_EXPLICITLY_ENABLED =
"content_capture_service_explicitly_enabled";
+ /** {@hide} */
+ public static final String ISOLATED_STORAGE_LOCAL = "isolated_storage_local";
+ /** {@hide} */
+ public static final String ISOLATED_STORAGE_REMOTE = "isolated_storage_remote";
+
/**
* Settings to backup. This is here so that it's in the same place as the settings
* keys and easy to update.
@@ -13901,7 +13955,6 @@ public final class Settings {
*/
public static final String LAST_ACTIVE_USER_ID = "last_active_persistent_user_id";
-
/**
* Whether we've enabled native flags health check on this device. Takes effect on
* reboot. The value "1" enables native flags health check; otherwise it's disabled.
@@ -13909,7 +13962,6 @@ public final class Settings {
*/
public static final String NATIVE_FLAGS_HEALTH_CHECK_ENABLED =
"native_flags_health_check_enabled";
-
}
/**
@@ -13920,21 +13972,12 @@ public final class Settings {
* @hide
*/
public static final class Config extends NameValueTable {
- /**
- * The content:// style URL for the config table.
- *
- * TODO(b/113100523): Move this to DeviceConfig.java when it is added, and expose it as a
- * System API.
- */
- private static final Uri CONTENT_URI =
- Uri.parse("content://" + AUTHORITY + "/config");
-
private static final ContentProviderHolder sProviderHolder =
- new ContentProviderHolder(CONTENT_URI);
+ new ContentProviderHolder(DeviceConfig.CONTENT_URI);
// Populated lazily, guarded by class object:
private static final NameValueCache sNameValueCache = new NameValueCache(
- CONTENT_URI,
+ DeviceConfig.CONTENT_URI,
CALL_METHOD_GET_CONFIG,
CALL_METHOD_PUT_CONFIG,
sProviderHolder);
@@ -14008,7 +14051,7 @@ public final class Settings {
cp.call(resolver.getPackageName(), sProviderHolder.mUri.getAuthority(),
CALL_METHOD_RESET_CONFIG, null, arg);
} catch (RemoteException e) {
- Log.w(TAG, "Can't reset to defaults for " + CONTENT_URI, e);
+ Log.w(TAG, "Can't reset to defaults for " + DeviceConfig.CONTENT_URI, e);
}
}
}
diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java
index 3dfeeded5946..58848fce43d6 100644
--- a/core/java/android/service/contentcapture/ContentCaptureService.java
+++ b/core/java/android/service/contentcapture/ContentCaptureService.java
@@ -29,7 +29,9 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
+import android.view.contentcapture.ContentCaptureContext;
import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.ContentCaptureSessionId;
import java.util.List;
import java.util.Set;
@@ -45,7 +47,7 @@ public abstract class ContentCaptureService extends Service {
private static final String TAG = ContentCaptureService.class.getSimpleName();
- // TODO(b/111330312): STOPSHIP use dynamic value, or change to false
+ // TODO(b/121044306): STOPSHIP use dynamic value, or change to false
static final boolean DEBUG = true;
static final boolean VERBOSE = false;
@@ -64,15 +66,15 @@ public abstract class ContentCaptureService extends Service {
private final IContentCaptureService mInterface = new IContentCaptureService.Stub() {
@Override
- public void onSessionLifecycle(InteractionContext context, String sessionId)
+ public void onSessionLifecycle(ContentCaptureContext context, String sessionId)
throws RemoteException {
if (context != null) {
mHandler.sendMessage(
- obtainMessage(ContentCaptureService::handleOnCreateInteractionSession,
+ obtainMessage(ContentCaptureService::handleOnCreateSession,
ContentCaptureService.this, context, sessionId));
} else {
mHandler.sendMessage(
- obtainMessage(ContentCaptureService::handleOnDestroyInteractionSession,
+ obtainMessage(ContentCaptureService::handleOnDestroySession,
ContentCaptureService.this, sessionId));
}
}
@@ -175,15 +177,15 @@ public abstract class ContentCaptureService extends Service {
}
/**
- * Creates a new interaction session.
+ * Creates a new content capture session.
*
- * @param context interaction context
+ * @param context content capture context
* @param sessionId the session's Id
*/
- public void onCreateInteractionSession(@NonNull InteractionContext context,
- @NonNull InteractionSessionId sessionId) {
+ public void onCreateContentCaptureSession(@NonNull ContentCaptureContext context,
+ @NonNull ContentCaptureSessionId sessionId) {
if (VERBOSE) {
- Log.v(TAG, "onCreateInteractionSession(id=" + sessionId + ", ctx=" + context + ")");
+ Log.v(TAG, "onCreateContentCaptureSession(id=" + sessionId + ", ctx=" + context + ")");
}
}
@@ -194,7 +196,7 @@ public abstract class ContentCaptureService extends Service {
* @param sessionId the session's Id
* @param request the events
*/
- public abstract void onContentCaptureEventsRequest(@NonNull InteractionSessionId sessionId,
+ public abstract void onContentCaptureEventsRequest(@NonNull ContentCaptureSessionId sessionId,
@NonNull ContentCaptureEventsRequest request);
/**
@@ -203,39 +205,39 @@ public abstract class ContentCaptureService extends Service {
* @param sessionId the session's Id
* @param snapshotData the data
*/
- public void onActivitySnapshot(@NonNull InteractionSessionId sessionId,
+ public void onActivitySnapshot(@NonNull ContentCaptureSessionId sessionId,
@NonNull SnapshotData snapshotData) {}
/**
- * Destroys the interaction session.
+ * Destroys the content capture session.
*
* @param sessionId the id of the session to destroy
*/
- public void onDestroyInteractionSession(@NonNull InteractionSessionId sessionId) {
+ public void onDestroyContentCaptureSession(@NonNull ContentCaptureSessionId sessionId) {
if (VERBOSE) {
- Log.v(TAG, "onDestroyInteractionSession(id=" + sessionId + ")");
+ Log.v(TAG, "onDestroyContentCaptureSession(id=" + sessionId + ")");
}
}
//TODO(b/111276913): consider caching the InteractionSessionId for the lifetime of the session,
// so we don't need to create a temporary InteractionSessionId for each event.
- private void handleOnCreateInteractionSession(@NonNull InteractionContext context,
+ private void handleOnCreateSession(@NonNull ContentCaptureContext context,
@NonNull String sessionId) {
- onCreateInteractionSession(context, new InteractionSessionId(sessionId));
+ onCreateContentCaptureSession(context, new ContentCaptureSessionId(sessionId));
}
private void handleOnContentCaptureEventsRequest(@NonNull String sessionId,
@NonNull ContentCaptureEventsRequest request) {
- onContentCaptureEventsRequest(new InteractionSessionId(sessionId), request);
+ onContentCaptureEventsRequest(new ContentCaptureSessionId(sessionId), request);
}
private void handleOnActivitySnapshot(@NonNull String sessionId,
@NonNull SnapshotData snapshotData) {
- onActivitySnapshot(new InteractionSessionId(sessionId), snapshotData);
+ onActivitySnapshot(new ContentCaptureSessionId(sessionId), snapshotData);
}
- private void handleOnDestroyInteractionSession(@NonNull String sessionId) {
- onDestroyInteractionSession(new InteractionSessionId(sessionId));
+ private void handleOnDestroySession(@NonNull String sessionId) {
+ onDestroyContentCaptureSession(new ContentCaptureSessionId(sessionId));
}
}
diff --git a/core/java/android/service/contentcapture/IContentCaptureService.aidl b/core/java/android/service/contentcapture/IContentCaptureService.aidl
index 29e9abbc6263..8167be9e6838 100644
--- a/core/java/android/service/contentcapture/IContentCaptureService.aidl
+++ b/core/java/android/service/contentcapture/IContentCaptureService.aidl
@@ -17,8 +17,8 @@
package android.service.contentcapture;
import android.service.contentcapture.ContentCaptureEventsRequest;
-import android.service.contentcapture.InteractionContext;
import android.service.contentcapture.SnapshotData;
+import android.view.contentcapture.ContentCaptureContext;
import java.util.List;
@@ -30,7 +30,7 @@ import java.util.List;
oneway interface IContentCaptureService {
// Called when session is created (context not null) or destroyed (context null)
- void onSessionLifecycle(in InteractionContext context, String sessionId);
+ void onSessionLifecycle(in ContentCaptureContext context, String sessionId);
void onContentCaptureEventsRequest(String sessionId, in ContentCaptureEventsRequest request);
diff --git a/core/java/android/service/contentcapture/InteractionContext.java b/core/java/android/service/contentcapture/InteractionContext.java
deleted file mode 100644
index f1281ff01033..000000000000
--- a/core/java/android/service/contentcapture/InteractionContext.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.service.contentcapture;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.SystemApi;
-import android.app.TaskInfo;
-import android.content.ComponentName;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.Preconditions;
-
-import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-// TODO(b/111276913): add javadocs / implement Parcelable / implement equals/hashcode/toString
-/** @hide */
-@SystemApi
-public final class InteractionContext implements Parcelable {
-
- /**
- * Flag used to indicate that the app explicitly disabled content capture for the activity
- * (using
- * {@link android.view.contentcapture.ContentCaptureManager#setContentCaptureEnabled(boolean)}),
- * in which case the service will just receive activity-level events.
- */
- public static final int FLAG_DISABLED_BY_APP = 0x1;
-
- /**
- * Flag used to indicate that the activity's window is tagged with
- * {@link android.view.Display#FLAG_SECURE}, in which case the service will just receive
- * activity-level events.
- */
- public static final int FLAG_DISABLED_BY_FLAG_SECURE = 0x2;
-
- /** @hide */
- @IntDef(flag = true, prefix = { "FLAG_" }, value = {
- FLAG_DISABLED_BY_APP,
- FLAG_DISABLED_BY_FLAG_SECURE
- })
- @Retention(RetentionPolicy.SOURCE)
- @interface ContextCreationFlags{}
-
- // TODO(b/111276913): create new object for taskId + componentName / reuse on other places
- private final @NonNull ComponentName mComponentName;
- private final int mTaskId;
- private final int mDisplayId;
- private final int mFlags;
-
-
- /** @hide */
- public InteractionContext(@NonNull ComponentName componentName, int taskId, int displayId,
- int flags) {
- mComponentName = Preconditions.checkNotNull(componentName);
- mTaskId = taskId;
- mDisplayId = displayId;
- mFlags = flags;
- }
-
- /**
- * Gets the id of the {@link TaskInfo task} associated with this context.
- */
- public int getTaskId() {
- return mTaskId;
- }
-
- /**
- * Gets the activity associated with this context.
- */
- public @NonNull ComponentName getActivityComponent() {
- return mComponentName;
- }
-
- /**
- * Gets the ID of the display associated with this context, as defined by
- * {G android.hardware.display.DisplayManager#getDisplay(int) DisplayManager.getDisplay()}.
- */
- public int getDisplayId() {
- return mDisplayId;
- }
-
- /**
- * Gets the flags associated with this context.
- *
- * @return any combination of {@link #FLAG_DISABLED_BY_FLAG_SECURE} and
- * {@link #FLAG_DISABLED_BY_APP}.
- */
- public @ContextCreationFlags int getFlags() {
- return mFlags;
- }
-
- /**
- * @hide
- */
- // TODO(b/111276913): dump to proto as well
- public void dump(PrintWriter pw) {
- pw.print("comp="); pw.print(mComponentName.flattenToShortString());
- pw.print(", taskId="); pw.print(mTaskId);
- pw.print(", displayId="); pw.print(mDisplayId);
- if (mFlags > 0) {
- pw.print(", flags="); pw.print(mFlags);
- }
- }
-
- @Override
- public String toString() {
- return "Context[act=" + mComponentName.flattenToShortString() + ", taskId=" + mTaskId
- + ", displayId=" + mDisplayId + ", flags=" + mFlags + "]";
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeParcelable(mComponentName, flags);
- parcel.writeInt(mTaskId);
- parcel.writeInt(mDisplayId);
- parcel.writeInt(mFlags);
- }
-
- public static final Parcelable.Creator<InteractionContext> CREATOR =
- new Parcelable.Creator<InteractionContext>() {
-
- @Override
- public InteractionContext createFromParcel(Parcel parcel) {
- final ComponentName componentName = parcel.readParcelable(null);
- final int taskId = parcel.readInt();
- final int displayId = parcel.readInt();
- final int flags = parcel.readInt();
- return new InteractionContext(componentName, taskId, displayId, flags);
- }
-
- @Override
- public InteractionContext[] newArray(int size) {
- return new InteractionContext[size];
- }
- };
-}
diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java
index af7e93e0ed74..30d98045f3f4 100644
--- a/core/java/android/service/notification/Condition.java
+++ b/core/java/android/service/notification/Condition.java
@@ -29,7 +29,7 @@ import java.util.Objects;
/**
* The current condition of an {@link android.app.AutomaticZenRule}, provided by the
- * {@link ConditionProviderService} that owns the rule. Used to tell the system to enter Do Not
+ * app that owns the rule. Used to tell the system to enter Do Not
* Disturb mode and request that the system exit Do Not Disturb mode.
*/
public final class Condition implements Parcelable {
@@ -48,8 +48,8 @@ public final class Condition implements Parcelable {
/**
* Indicates that Do Not Disturb should be turned off. Note that all Conditions from all
- * {@link ConditionProviderService} providers must be off for Do Not Disturb to be turned off on
- * the device.
+ * {@link android.app.AutomaticZenRule} providers must be off for Do Not Disturb to be turned
+ * off on the device.
*/
public static final int STATE_FALSE = 0;
/**
@@ -154,7 +154,7 @@ public final class Condition implements Parcelable {
public void writeToProto(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
- // id is guarantreed not to be null.
+ // id is guaranteed not to be null.
proto.write(ConditionProto.ID, id.toString());
proto.write(ConditionProto.SUMMARY, summary);
proto.write(ConditionProto.LINE_1, line1);
diff --git a/core/java/android/service/notification/ConditionProviderService.java b/core/java/android/service/notification/ConditionProviderService.java
index 5203c8f4bb27..45480cb5acb7 100644
--- a/core/java/android/service/notification/ConditionProviderService.java
+++ b/core/java/android/service/notification/ConditionProviderService.java
@@ -59,7 +59,16 @@ import android.util.Log;
*
* <p> Condition providers cannot be bound by the system on
* {@link ActivityManager#isLowRamDevice() low ram} devices</p>
+ *
+ * @deprecated Instead of using an automatically bound service, use
+ * {@link android.app.NotificationManager#setAutomaticZenRuleState(String, Condition)} to tell the
+ * system about the state of your rule. In order to maintain a link from
+ * Settings to your rule configuration screens, provide a configuration activity that handles
+ * {@link android.app.NotificationManager#ACTION_AUTOMATIC_ZEN_RULE} on your
+ * {@link android.app.AutomaticZenRule} via
+ * {@link android.app.AutomaticZenRule#setConfigurationActivity(ComponentName)}.
*/
+@Deprecated
public abstract class ConditionProviderService extends Service {
private final String TAG = ConditionProviderService.class.getSimpleName()
+ "[" + getClass().getSimpleName() + "]";
@@ -79,26 +88,38 @@ public abstract class ConditionProviderService extends Service {
/**
* The name of the {@code meta-data} tag containing a localized name of the type of zen rules
* provided by this service.
+ *
+ * @deprecated see {@link android.app.NotificationManager#META_DATA_AUTOMATIC_RULE_TYPE}.
*/
+ @Deprecated
public static final String META_DATA_RULE_TYPE = "android.service.zen.automatic.ruleType";
/**
* The name of the {@code meta-data} tag containing the {@link ComponentName} of an activity
* that allows users to configure the conditions provided by this service.
+ *
+ * @deprecated see {@link android.app.NotificationManager#ACTION_AUTOMATIC_ZEN_RULE}.
*/
+ @Deprecated
public static final String META_DATA_CONFIGURATION_ACTIVITY =
"android.service.zen.automatic.configurationActivity";
/**
* The name of the {@code meta-data} tag containing the maximum number of rule instances that
* can be created for this rule type. Omit or enter a value <= 0 to allow unlimited instances.
+ *
+ * @deprecated see {@link android.app.NotificationManager#META_DATA_RULE_INSTANCE_LIMIT}.
*/
+ @Deprecated
public static final String META_DATA_RULE_INSTANCE_LIMIT =
"android.service.zen.automatic.ruleInstanceLimit";
/**
* A String rule id extra passed to {@link #META_DATA_CONFIGURATION_ACTIVITY}.
+ *
+ * @deprecated see {@link android.app.NotificationManager#EXTRA_AUTOMATIC_RULE_ID}.
*/
+ @Deprecated
public static final String EXTRA_RULE_ID = "android.service.notification.extra.RULE_ID";
/**
@@ -171,7 +192,11 @@ public abstract class ConditionProviderService extends Service {
* service that has an {@link android.app.AutomaticZenRule#getConditionId()} equal to this
* {@link Condition#id}.
* @param condition the condition that has changed.
+ *
+ * @deprecated see
+ * {@link android.app.NotificationManager#setAutomaticZenRuleState(String, Condition)}.
*/
+ @Deprecated
public final void notifyCondition(Condition condition) {
if (condition == null) return;
notifyConditions(new Condition[]{ condition });
@@ -181,7 +206,11 @@ public abstract class ConditionProviderService extends Service {
* Informs the notification manager that the state of one or more Conditions has changed. See
* {@link #notifyCondition(Condition)} for restrictions.
* @param conditions the changed conditions.
+ *
+ * @deprecated see
+ * {@link android.app.NotificationManager#setAutomaticZenRuleState(String, Condition)}.
*/
+ @Deprecated
public final void notifyConditions(Condition... conditions) {
if (!isBound() || conditions == null) return;
try {
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 1fe97b79fd69..6d2f85679cff 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1482,7 +1482,7 @@ public abstract class NotificationListenerService extends Service {
private boolean mShowBadge;
private @UserSentiment int mUserSentiment = USER_SENTIMENT_NEUTRAL;
private boolean mHidden;
- private boolean mAudiblyAlerted;
+ private long mLastAudiblyAlertedMs;
private boolean mNoisy;
private ArrayList<Notification.Action> mSmartActions;
private ArrayList<CharSequence> mSmartReplies;
@@ -1650,12 +1650,12 @@ public abstract class NotificationListenerService extends Service {
}
/**
- * Returns whether this notification alerted the user via sound or vibration.
+ * Returns the last time this notification alerted the user via sound or vibration.
*
- * @return true if the notification alerted the user, false otherwise.
+ * @return the time of the last alerting behavior, in milliseconds.
*/
- public boolean audiblyAlerted() {
- return mAudiblyAlerted;
+ public long getLastAudiblyAlertedMillis() {
+ return mLastAudiblyAlertedMs;
}
/** @hide */
@@ -1672,7 +1672,7 @@ public abstract class NotificationListenerService extends Service {
CharSequence explanation, String overrideGroupKey,
NotificationChannel channel, ArrayList<String> overridePeople,
ArrayList<SnoozeCriterion> snoozeCriteria, boolean showBadge,
- int userSentiment, boolean hidden, boolean audiblyAlerted,
+ int userSentiment, boolean hidden, long lastAudiblyAlertedMs,
boolean noisy, ArrayList<Notification.Action> smartActions,
ArrayList<CharSequence> smartReplies) {
mKey = key;
@@ -1690,7 +1690,7 @@ public abstract class NotificationListenerService extends Service {
mShowBadge = showBadge;
mUserSentiment = userSentiment;
mHidden = hidden;
- mAudiblyAlerted = audiblyAlerted;
+ mLastAudiblyAlertedMs = lastAudiblyAlertedMs;
mNoisy = noisy;
mSmartActions = smartActions;
mSmartReplies = smartReplies;
@@ -1743,7 +1743,7 @@ public abstract class NotificationListenerService extends Service {
private ArrayMap<String, Boolean> mShowBadge;
private ArrayMap<String, Integer> mUserSentiment;
private ArrayMap<String, Boolean> mHidden;
- private ArrayMap<String, Boolean> mAudiblyAlerted;
+ private ArrayMap<String, Long> mLastAudiblyAlerted;
private ArrayMap<String, Boolean> mNoisy;
private ArrayMap<String, ArrayList<Notification.Action>> mSmartActions;
private ArrayMap<String, ArrayList<CharSequence>> mSmartReplies;
@@ -1776,7 +1776,7 @@ public abstract class NotificationListenerService extends Service {
getImportance(key), getImportanceExplanation(key), getOverrideGroupKey(key),
getChannel(key), getOverridePeople(key), getSnoozeCriteria(key),
getShowBadge(key), getUserSentiment(key), getHidden(key),
- getAudiblyAlerted(key), getNoisy(key), getSmartActions(key),
+ getLastAudiblyAlerted(key), getNoisy(key), getSmartActions(key),
getSmartReplies(key));
return rank >= 0;
}
@@ -1915,14 +1915,14 @@ public abstract class NotificationListenerService extends Service {
return hidden == null ? false : hidden.booleanValue();
}
- private boolean getAudiblyAlerted(String key) {
+ private long getLastAudiblyAlerted(String key) {
synchronized (this) {
- if (mAudiblyAlerted == null) {
- buildAudiblyAlertedLocked();
+ if (mLastAudiblyAlerted == null) {
+ buildLastAudiblyAlertedLocked();
}
}
- Boolean audiblyAlerted = mAudiblyAlerted.get(key);
- return audiblyAlerted == null ? false : audiblyAlerted.booleanValue();
+ Long lastAudibleAlerted = mLastAudiblyAlerted.get(key);
+ return lastAudibleAlerted == null ? -1 : lastAudibleAlerted.longValue();
}
private boolean getNoisy(String key) {
@@ -1994,6 +1994,14 @@ public abstract class NotificationListenerService extends Service {
return newMap;
}
+ private ArrayMap<String, Long> buildLongMapFromBundle(Bundle bundle) {
+ ArrayMap<String, Long> newMap = new ArrayMap<>(bundle.size());
+ for (String key : bundle.keySet()) {
+ newMap.put(key, bundle.getLong(key));
+ }
+ return newMap;
+ }
+
// Locked by 'this'
private void buildVisibilityOverridesLocked() {
mVisibilityOverrides = buildIntMapFromBundle(mRankingUpdate.getVisibilityOverrides());
@@ -2070,8 +2078,8 @@ public abstract class NotificationListenerService extends Service {
}
// Locked by 'this'
- private void buildAudiblyAlertedLocked() {
- mAudiblyAlerted = buildBooleanMapFromBundle(mRankingUpdate.getAudiblyAlerted());
+ private void buildLastAudiblyAlertedLocked() {
+ mLastAudiblyAlerted = buildLongMapFromBundle(mRankingUpdate.getLastAudiblyAlerted());
}
// Locked by 'this'
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index f80df93364f4..ebaeff8ff33c 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -39,7 +39,7 @@ public class NotificationRankingUpdate implements Parcelable {
private final Bundle mHidden;
private final Bundle mSmartActions;
private final Bundle mSmartReplies;
- private final Bundle mAudiblyAlerted;
+ private final Bundle mLastAudiblyAlerted;
private final Bundle mNoisy;
public NotificationRankingUpdate(String[] keys, String[] interceptedKeys,
@@ -47,7 +47,7 @@ public class NotificationRankingUpdate implements Parcelable {
int[] importance, Bundle explanation, Bundle overrideGroupKeys,
Bundle channels, Bundle overridePeople, Bundle snoozeCriteria,
Bundle showBadge, Bundle userSentiment, Bundle hidden, Bundle smartActions,
- Bundle smartReplies, Bundle audiblyAlerted, Bundle noisy) {
+ Bundle smartReplies, Bundle lastAudiblyAlerted, Bundle noisy) {
mKeys = keys;
mInterceptedKeys = interceptedKeys;
mVisibilityOverrides = visibilityOverrides;
@@ -63,7 +63,7 @@ public class NotificationRankingUpdate implements Parcelable {
mHidden = hidden;
mSmartActions = smartActions;
mSmartReplies = smartReplies;
- mAudiblyAlerted = audiblyAlerted;
+ mLastAudiblyAlerted = lastAudiblyAlerted;
mNoisy = noisy;
}
@@ -84,7 +84,7 @@ public class NotificationRankingUpdate implements Parcelable {
mHidden = in.readBundle();
mSmartActions = in.readBundle();
mSmartReplies = in.readBundle();
- mAudiblyAlerted = in.readBundle();
+ mLastAudiblyAlerted = in.readBundle();
mNoisy = in.readBundle();
}
@@ -110,7 +110,7 @@ public class NotificationRankingUpdate implements Parcelable {
out.writeBundle(mHidden);
out.writeBundle(mSmartActions);
out.writeBundle(mSmartReplies);
- out.writeBundle(mAudiblyAlerted);
+ out.writeBundle(mLastAudiblyAlerted);
out.writeBundle(mNoisy);
}
@@ -185,8 +185,8 @@ public class NotificationRankingUpdate implements Parcelable {
return mSmartReplies;
}
- public Bundle getAudiblyAlerted() {
- return mAudiblyAlerted;
+ public Bundle getLastAudiblyAlerted() {
+ return mLastAudiblyAlerted;
}
public Bundle getNoisy() {
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 0e2ae837a141..6792c6935c27 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -142,6 +142,7 @@ public class ZenModeConfig implements Parcelable {
private static final String RULE_ATT_SNOOZING = "snoozing";
private static final String RULE_ATT_NAME = "name";
private static final String RULE_ATT_COMPONENT = "component";
+ private static final String RULE_ATT_CONFIG_ACTIVITY = "configActivity";
private static final String RULE_ATT_ZEN = "zen";
private static final String RULE_ATT_CONDITION_ID = "conditionId";
private static final String RULE_ATT_CREATION_TIME = "creationTime";
@@ -269,7 +270,7 @@ public class ZenModeConfig implements Parcelable {
return buffer.toString();
}
- private Diff diff(ZenModeConfig to) {
+ public Diff diff(ZenModeConfig to) {
final Diff d = new Diff();
if (to == null) {
return d.addLine("config", "delete");
@@ -623,7 +624,6 @@ public class ZenModeConfig implements Parcelable {
public static ZenRule readRuleXml(XmlPullParser parser) {
final ZenRule rt = new ZenRule();
rt.enabled = safeBoolean(parser, RULE_ATT_ENABLED, true);
- rt.snoozing = safeBoolean(parser, RULE_ATT_SNOOZING, false);
rt.name = parser.getAttributeValue(null, RULE_ATT_NAME);
final String zen = parser.getAttributeValue(null, RULE_ATT_ZEN);
rt.zenMode = tryParseZenMode(zen, -1);
@@ -633,6 +633,12 @@ public class ZenModeConfig implements Parcelable {
}
rt.conditionId = safeUri(parser, RULE_ATT_CONDITION_ID);
rt.component = safeComponentName(parser, RULE_ATT_COMPONENT);
+ rt.configurationActivity = safeComponentName(parser, RULE_ATT_CONFIG_ACTIVITY);
+ rt.pkg = (rt.component != null)
+ ? rt.component.getPackageName()
+ : (rt.configurationActivity != null)
+ ? rt.configurationActivity.getPackageName()
+ : null;
rt.creationTime = safeLong(parser, RULE_ATT_CREATION_TIME, 0);
rt.enabler = parser.getAttributeValue(null, RULE_ATT_ENABLER);
rt.condition = readConditionXml(parser);
@@ -649,7 +655,6 @@ public class ZenModeConfig implements Parcelable {
public static void writeRuleXml(ZenRule rule, XmlSerializer out) throws IOException {
out.attribute(null, RULE_ATT_ENABLED, Boolean.toString(rule.enabled));
- out.attribute(null, RULE_ATT_SNOOZING, Boolean.toString(rule.snoozing));
if (rule.name != null) {
out.attribute(null, RULE_ATT_NAME, rule.name);
}
@@ -657,6 +662,10 @@ public class ZenModeConfig implements Parcelable {
if (rule.component != null) {
out.attribute(null, RULE_ATT_COMPONENT, rule.component.flattenToString());
}
+ if (rule.configurationActivity != null) {
+ out.attribute(null, RULE_ATT_CONFIG_ACTIVITY,
+ rule.configurationActivity.flattenToString());
+ }
if (rule.conditionId != null) {
out.attribute(null, RULE_ATT_CONDITION_ID, rule.conditionId.toString());
}
@@ -1452,12 +1461,15 @@ public class ZenModeConfig implements Parcelable {
public Uri conditionId; // required for automatic
public Condition condition; // optional
public ComponentName component; // optional
+ public ComponentName configurationActivity; // optional
public String id; // required for automatic (unique)
@UnsupportedAppUsage
public long creationTime; // required for automatic
- public String enabler; // package name, only used for manual rules.
+ // package name, only used for manual rules when they have turned DND on.
+ public String enabler;
public ZenPolicy zenPolicy;
public boolean modified; // rule has been modified from initial creation
+ public String pkg;
public ZenRule() { }
@@ -1471,6 +1483,7 @@ public class ZenModeConfig implements Parcelable {
conditionId = source.readParcelable(null);
condition = source.readParcelable(null);
component = source.readParcelable(null);
+ configurationActivity = source.readParcelable(null);
if (source.readInt() == 1) {
id = source.readString();
}
@@ -1480,6 +1493,7 @@ public class ZenModeConfig implements Parcelable {
}
zenPolicy = source.readParcelable(null);
modified = source.readInt() == 1;
+ pkg = source.readString();
}
@Override
@@ -1501,6 +1515,7 @@ public class ZenModeConfig implements Parcelable {
dest.writeParcelable(conditionId, 0);
dest.writeParcelable(condition, 0);
dest.writeParcelable(component, 0);
+ dest.writeParcelable(configurationActivity, 0);
if (id != null) {
dest.writeInt(1);
dest.writeString(id);
@@ -1516,6 +1531,7 @@ public class ZenModeConfig implements Parcelable {
}
dest.writeParcelable(zenPolicy, 0);
dest.writeInt(modified ? 1 : 0);
+ dest.writeString(pkg);
}
@Override
@@ -1528,7 +1544,9 @@ public class ZenModeConfig implements Parcelable {
.append(",zenMode=").append(Global.zenModeToString(zenMode))
.append(",conditionId=").append(conditionId)
.append(",condition=").append(condition)
+ .append(",pkg=").append(pkg)
.append(",component=").append(component)
+ .append(",configActivity=").append(configurationActivity)
.append(",creationTime=").append(creationTime)
.append(",enabler=").append(enabler)
.append(",zenPolicy=").append(zenPolicy)
@@ -1537,6 +1555,7 @@ public class ZenModeConfig implements Parcelable {
}
/** @hide */
+ // TODO: add configuration activity
public void writeToProto(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
@@ -1600,6 +1619,9 @@ public class ZenModeConfig implements Parcelable {
if (!Objects.equals(component, to.component)) {
d.addLine(item, "component", component, to.component);
}
+ if (!Objects.equals(configurationActivity, to.configurationActivity)) {
+ d.addLine(item, "configActivity", configurationActivity, to.configurationActivity);
+ }
if (!Objects.equals(id, to.id)) {
d.addLine(item, "id", id, to.id);
}
@@ -1615,6 +1637,9 @@ public class ZenModeConfig implements Parcelable {
if (modified != to.modified) {
d.addLine(item, "modified", modified, to.modified);
}
+ if (pkg != to.pkg) {
+ d.addLine(item, "pkg", pkg, to.pkg);
+ }
}
@Override
@@ -1629,20 +1654,22 @@ public class ZenModeConfig implements Parcelable {
&& Objects.equals(other.conditionId, conditionId)
&& Objects.equals(other.condition, condition)
&& Objects.equals(other.component, component)
+ && Objects.equals(other.configurationActivity, configurationActivity)
&& Objects.equals(other.id, id)
&& Objects.equals(other.enabler, enabler)
&& Objects.equals(other.zenPolicy, zenPolicy)
+ && Objects.equals(other.pkg, pkg)
&& other.modified == modified;
}
@Override
public int hashCode() {
return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
- component, id, enabler, zenPolicy, modified);
+ component, configurationActivity, pkg, id, enabler, zenPolicy, modified);
}
public boolean isAutomaticActive() {
- return enabled && !snoozing && component != null && isTrueOrUnknown();
+ return enabled && !snoozing && pkg != null && isTrueOrUnknown();
}
public boolean isTrueOrUnknown() {
diff --git a/core/java/android/text/style/LineBackgroundSpan.java b/core/java/android/text/style/LineBackgroundSpan.java
index 5a55fd749150..e43fd8303515 100644
--- a/core/java/android/text/style/LineBackgroundSpan.java
+++ b/core/java/android/text/style/LineBackgroundSpan.java
@@ -118,7 +118,7 @@ public interface LineBackgroundSpan extends ParagraphStyle
int lineNumber) {
final int originColor = paint.getColor();
paint.setColor(mColor);
- canvas.drawRect(left, right, top, bottom, paint);
+ canvas.drawRect(left, top, right, bottom, paint);
paint.setColor(originColor);
}
}
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 5e6d3d19fa2f..c06a1fe0a257 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -71,41 +71,18 @@ oneway interface IWindow {
void dispatchGetNewSurface();
/**
- * Tell the window that it is either gaining or losing focus.
- *
- * @param hasFocus {@code true} if window has focus, {@code false} otherwise.
- * @param inTouchMode {@code true} if screen is in touch mode, {@code false} otherwise.
- * @param reportToClient {@code true} when need to report to child view with
- * {@link View#onWindowFocusChanged(boolean)}, {@code false} otherwise.
- * <p>
- * Note: In the previous design, there is only one window focus state tracked by
- * WindowManagerService.
- * For multi-display, the window focus state is tracked by each display independently.
- * <p>
- * It will introduce a problem if the window was already focused on one display and then
- * switched to another display, since the window focus state on each display is independent,
- * there is no global window focus state in WindowManagerService, so the window focus state of
- * the former display remains unchanged.
- * <p>
- * When switched back to former display, some flows that rely on the global window focus state
- * in view root will be missed due to the window focus state remaining unchanged.
- * (i.e: Showing single IME window when switching between displays.)
- * <p>
- * To solve the problem, WindowManagerService tracks the top focused display change and then
- * callbacks to the client via this method to make sure that the client side will request the
- * IME on the top focused display, and then set {@param reportToClient} as {@code false} to
- * ignore reporting to the application, since its focus remains unchanged on its display.
- *
+ * Tell the window that it is either gaining or losing focus. Keep it up
+ * to date on the current state showing navigational focus (touch mode) too.
*/
- void windowFocusChanged(boolean hasFocus, boolean inTouchMode, boolean reportToClient);
-
+ void windowFocusChanged(boolean hasFocus, boolean inTouchMode);
+
void closeSystemDialogs(String reason);
-
+
/**
* Called for wallpaper windows when their offsets change.
*/
void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep, boolean sync);
-
+
void dispatchWallpaperCommand(String action, int x, int y,
int z, in Bundle extras, boolean sync);
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index 4a5ccdff0dd3..ada78532d63f 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -340,7 +340,7 @@ public class NotificationHeaderView extends ViewGroup {
}
/** Updates icon visibility based on the noisiness of the notification. */
- public void setAudiblyAlerted(boolean audiblyAlerted) {
+ public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) {
mAudiblyAlertedIcon.setVisibility(audiblyAlerted ? View.VISIBLE : View.GONE);
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 4297efb71ad0..468d92290c13 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -109,7 +109,9 @@ import android.view.animation.Transformation;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
+import android.view.contentcapture.ContentCaptureContext;
import android.view.contentcapture.ContentCaptureManager;
+import android.view.contentcapture.ContentCaptureSession;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
@@ -121,6 +123,7 @@ import android.widget.FrameLayout;
import android.widget.ScrollBarDrawable;
import com.android.internal.R;
+import com.android.internal.util.Preconditions;
import com.android.internal.view.TooltipPopup;
import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.widget.ScrollBarUtils;
@@ -5018,6 +5021,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private Handler mVisibilityChangeForAutofillHandler;
/**
+ * Used when app developers explicitly set the {@link ContentCaptureSession} associated with the
+ * view (through {@link #setContentCaptureSession(ContentCaptureSession)}.
+ */
+ @Nullable
+ private WeakReference<ContentCaptureSession> mContentCaptureSession;
+
+ /**
* Simple constructor to use when creating a view from code.
*
* @param context The Context the view is running in, through which it can
@@ -8161,7 +8171,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* is visible.
*
* <p>The populated structure is then passed to the service through
- * {@link ContentCaptureManager#notifyViewAppeared(ViewStructure)}.
+ * {@link ContentCaptureSession#notifyViewAppeared(ViewStructure)}.
*
* <p><b>Note: </b>the following methods of the {@code structure} will be ignored:
* <ul>
@@ -8977,13 +8987,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (!mContext.isContentCaptureSupported()) return;
// Then check if it's enabled in the context...
- final ContentCaptureManager cm = mContext.getSystemService(ContentCaptureManager.class);
- if (cm == null || !cm.isContentCaptureEnabled()) return;
+ final ContentCaptureManager ccm = mContext.getSystemService(ContentCaptureManager.class);
+ if (ccm == null || !ccm.isContentCaptureEnabled()) return;
// ... and finally at the view level
// NOTE: isImportantForContentCapture() is more expensive than cm.isContentCaptureEnabled()
if (!isImportantForContentCapture()) return;
+ final ContentCaptureSession session = getContentCaptureSession(ccm);
+ if (session == null) return;
+
if (appeared) {
if (!isLaidOut() || !isVisibleToUser()
|| (mPrivateFlags4 & PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED) != 0) {
@@ -8995,10 +9008,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
return;
}
- // All good: notify the manager...
- final ViewStructure structure = cm.newViewStructure(this);
+ // All good: notify it...
+ final ViewStructure structure = session.newViewStructure(this);
onProvideContentCaptureStructure(structure, /* flags= */ 0);
- cm.notifyViewAppeared(structure);
+ session.notifyViewAppeared(structure);
// ...and set the flags
mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED;
mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED;
@@ -9014,14 +9027,85 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
return;
}
- // All good: notify the manager...
- cm.notifyViewDisappeared(getAutofillId());
+ // All good: notify it...
+ session.notifyViewDisappeared(getAutofillId());
// ...and set the flags
mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED;
mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED;
}
}
+ /**
+ * Sets the (optional) {@link ContentCaptureSession} associated with this view.
+ *
+ * <p>This method should be called when you need to associate a {@link ContentCaptureContext} to
+ * the Content Capture events associated with this view or its view hierarchy (if it's a
+ * {@link ViewGroup}).
+ *
+ * <p>For example, if your activity is associated with a web domain, you could create a session
+ * {@code onCreate()} and associate it with the root view of the activity:
+ *
+ * <pre>
+ * ContentCaptureManager mgr = getSystemService(ContentCaptureManager.class);
+ * if (mgr != null && mgr.isContentCaptureEnabled()) {
+ * View rootView = findViewById(R.my_root_view);
+ * ContentCaptureSession session = mgr.createContentCaptureSession(new
+ * ContentCaptureContext.Builder().setUri(myUrl).build());
+ * rootView.setContentCaptureSession(session);
+ * }
+ * </pre>
+ *
+ * @param contentCaptureSession a session created by
+ * {@link ContentCaptureManager#createContentCaptureSession(
+ * android.view.contentcapture.ContentCaptureContext)}.
+ */
+ public void setContentCaptureSession(@NonNull ContentCaptureSession contentCaptureSession) {
+ mContentCaptureSession = new WeakReference<>(
+ Preconditions.checkNotNull(contentCaptureSession));
+ }
+
+ /**
+ * Gets the session used to notify Content Capture events.
+ *
+ * @return session explicitly set by {@link #setContentCaptureSession(ContentCaptureSession)},
+ * inherited by ancestore, default session or {@code null} if content capture is disabled for
+ * this view.
+ */
+ @Nullable
+ public final ContentCaptureSession getContentCaptureSession() {
+ // First try the session explicitly set by setContentCaptureSession()
+ if (mContentCaptureSession != null) return mContentCaptureSession.get();
+
+ // Then the session explicitly set in an ancestor
+ ContentCaptureSession session = null;
+ if (mParent instanceof View) {
+ session = ((View) mParent).getContentCaptureSession();
+ }
+
+ // Finally, if no session was explicitly set, use the context's default session.
+ if (session == null) {
+ final ContentCaptureManager ccm = mContext
+ .getSystemService(ContentCaptureManager.class);
+ return ccm == null ? null : ccm.getMainContentCaptureSession();
+ }
+ return session;
+ }
+
+ /**
+ * Optimized version of {@link #getContentCaptureSession()} that avoids a service lookup.
+ */
+ @Nullable
+ private ContentCaptureSession getContentCaptureSession(@NonNull ContentCaptureManager ccm) {
+ if (mContentCaptureSession != null) return mContentCaptureSession.get();
+
+ ContentCaptureSession session = null;
+ if (mParent instanceof View) {
+ session = ((View) mParent).getContentCaptureSession();
+ }
+
+ return session != null ? session : ccm.getMainContentCaptureSession();
+ }
+
@Nullable
private AutofillManager getAutofillManager() {
return mContext.getSystemService(AutofillManager.class);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9fe0ddc110c2..3f7a5127339d 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1876,6 +1876,7 @@ public final class ViewRootImpl implements ViewParent,
}
void dispatchApplyInsets(View host) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "dispatchApplyInsets");
WindowInsets insets = getWindowInsets(true /* forceConstruct */);
final boolean dispatchCutout = (mWindowAttributes.layoutInDisplayCutoutMode
== LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS);
@@ -1885,6 +1886,7 @@ public final class ViewRootImpl implements ViewParent,
insets = insets.consumeDisplayCutout();
}
host.dispatchApplyWindowInsets(insets);
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
InsetsController getInsetsController() {
@@ -2763,7 +2765,7 @@ public final class ViewRootImpl implements ViewParent,
}
}
- private void handleWindowFocusChanged(boolean reportToClient) {
+ private void handleWindowFocusChanged() {
final boolean hasWindowFocus;
final boolean inTouchMode;
synchronized (this) {
@@ -2798,9 +2800,8 @@ public final class ViewRootImpl implements ViewParent,
} catch (RemoteException ex) {
}
// Retry in a bit.
- final Message msg = mHandler.obtainMessage(MSG_WINDOW_FOCUS_CHANGED);
- msg.arg1 = reportToClient ? 1 : 0;
- mHandler.sendMessageDelayed(msg, 500);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(
+ MSG_WINDOW_FOCUS_CHANGED), 500);
return;
}
}
@@ -2817,15 +2818,8 @@ public final class ViewRootImpl implements ViewParent,
}
if (mView != null) {
mAttachInfo.mKeyDispatchState.reset();
- // We dispatch onWindowFocusChanged to child view only when window is gaining /
- // losing focus.
- // If the focus is updated from top display change but window focus on the display
- // remains unchanged, will not callback onWindowFocusChanged again since it may
- // be redundant & can affect the state when it callbacks.
- if (reportToClient) {
- mView.dispatchWindowFocusChanged(hasWindowFocus);
- mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
- }
+ mView.dispatchWindowFocusChanged(hasWindowFocus);
+ mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
if (mAttachInfo.mTooltipHost != null) {
mAttachInfo.mTooltipHost.hideTooltip();
}
@@ -4426,7 +4420,7 @@ public final class ViewRootImpl implements ViewParent,
}
break;
case MSG_WINDOW_FOCUS_CHANGED: {
- handleWindowFocusChanged(msg.arg1 != 0 /* reportToClient */);
+ handleWindowFocusChanged();
} break;
case MSG_DIE:
doDie();
@@ -7370,7 +7364,7 @@ public final class ViewRootImpl implements ViewParent,
}
if (stage != null) {
- handleWindowFocusChanged(true /* reportToClient */);
+ handleWindowFocusChanged();
stage.deliver(q);
} else {
finishInputEvent(q);
@@ -7710,11 +7704,6 @@ public final class ViewRootImpl implements ViewParent,
}
public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
- windowFocusChanged(hasFocus, inTouchMode, true /* reportToClient */);
- }
-
- public void windowFocusChanged(boolean hasFocus, boolean inTouchMode,
- boolean reportToClient) {
synchronized (this) {
mWindowFocusChanged = true;
mUpcomingWindowFocus = hasFocus;
@@ -7722,7 +7711,6 @@ public final class ViewRootImpl implements ViewParent,
}
Message msg = Message.obtain();
msg.what = MSG_WINDOW_FOCUS_CHANGED;
- msg.arg1 = reportToClient ? 1 : 0;
mHandler.sendMessage(msg);
}
@@ -8284,11 +8272,10 @@ public final class ViewRootImpl implements ViewParent,
}
@Override
- public void windowFocusChanged(boolean hasFocus, boolean inTouchMode,
- boolean reportToClient) {
+ public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
- viewAncestor.windowFocusChanged(hasFocus, inTouchMode, reportToClient);
+ viewAncestor.windowFocusChanged(hasFocus, inTouchMode);
}
}
diff --git a/core/java/android/service/contentcapture/InteractionContext.aidl b/core/java/android/view/contentcapture/ContentCaptureContext.aidl
index 982e095894fb..da492f502818 100644
--- a/core/java/android/service/contentcapture/InteractionContext.aidl
+++ b/core/java/android/view/contentcapture/ContentCaptureContext.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.service.contentcapture;
+package android.view.contentcapture;
-parcelable InteractionContext;
+parcelable ContentCaptureContext;
diff --git a/core/java/android/view/contentcapture/ContentCaptureContext.java b/core/java/android/view/contentcapture/ContentCaptureContext.java
new file mode 100644
index 000000000000..9c11743fdf19
--- /dev/null
+++ b/core/java/android/view/contentcapture/ContentCaptureContext.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.contentcapture;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.TaskInfo;
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.View;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Context associated with a {@link ContentCaptureSession}.
+ */
+public final class ContentCaptureContext implements Parcelable {
+
+ /*
+ * IMPLEMENTATION NOTICE:
+ *
+ * This object contains both the info that's explicitly added by apps (hence it's public), but
+ * it also contains info injected by the server (and are accessible through @SystemApi methods).
+ */
+
+ /**
+ * Flag used to indicate that the app explicitly disabled content capture for the activity
+ * (using
+ * {@link android.view.contentcapture.ContentCaptureManager#setContentCaptureEnabled(boolean)}),
+ * in which case the service will just receive activity-level events.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_DISABLED_BY_APP = 0x1;
+
+ /**
+ * Flag used to indicate that the activity's window is tagged with
+ * {@link android.view.Display#FLAG_SECURE}, in which case the service will just receive
+ * activity-level events.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int FLAG_DISABLED_BY_FLAG_SECURE = 0x2;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "FLAG_" }, value = {
+ FLAG_DISABLED_BY_APP,
+ FLAG_DISABLED_BY_FLAG_SECURE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface ContextCreationFlags{}
+
+ /**
+ * Flag indicating if this object has the app-provided context (which is set on
+ * {@link ContentCaptureManager#createContentCaptureSession(ContentCaptureContext)}).
+ */
+ private final boolean mHasClientContext;
+
+ // Fields below are set by app on Builder
+ private final @Nullable Bundle mExtras;
+ private final @Nullable Uri mUri;
+
+ // Fields below are set by server when the session starts
+ // TODO(b/111276913): create new object for taskId + componentName / reuse on other places
+ private final @Nullable ComponentName mComponentName;
+ private final int mTaskId;
+ private final int mDisplayId;
+ private final int mFlags;
+
+ /** @hide */
+ public ContentCaptureContext(@Nullable ContentCaptureContext clientContext,
+ @NonNull ComponentName componentName, int taskId, int displayId, int flags) {
+ if (clientContext != null) {
+ mHasClientContext = true;
+ mExtras = clientContext.mExtras;
+ mUri = clientContext.mUri;
+ } else {
+ mHasClientContext = false;
+ mExtras = null;
+ mUri = null;
+ }
+ mComponentName = Preconditions.checkNotNull(componentName);
+ mTaskId = taskId;
+ mDisplayId = displayId;
+ mFlags = flags;
+ }
+
+ private ContentCaptureContext(@NonNull Builder builder) {
+ mHasClientContext = true;
+ mExtras = builder.mExtras;
+ mUri = builder.mUri;
+
+ mComponentName = null;
+ mTaskId = mFlags = mDisplayId = 0;
+ }
+
+ /**
+ * Gets the (optional) extras set by the app.
+ *
+ * <p>It can be used to provide vendor-specific data that can be modified and examined.
+ *
+ * @hide
+ */
+ @SystemApi
+ @Nullable
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ /**
+ * Gets the (optional) URI set by the app.
+ *
+ * @hide
+ */
+ @SystemApi
+ @Nullable
+ public Uri getUri() {
+ return mUri;
+ }
+
+ /**
+ * Gets the id of the {@link TaskInfo task} associated with this context.
+ *
+ * @hide
+ */
+ @SystemApi
+ public int getTaskId() {
+ return mTaskId;
+ }
+
+ /**
+ * Gets the activity associated with this context.
+ *
+ * @hide
+ */
+ @SystemApi
+ public @NonNull ComponentName getActivityComponent() {
+ return mComponentName;
+ }
+
+ /**
+ * Gets the ID of the display associated with this context, as defined by
+ * {G android.hardware.display.DisplayManager#getDisplay(int) DisplayManager.getDisplay()}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public int getDisplayId() {
+ return mDisplayId;
+ }
+
+ /**
+ * Gets the flags associated with this context.
+ *
+ * @return any combination of {@link #FLAG_DISABLED_BY_FLAG_SECURE} and
+ * {@link #FLAG_DISABLED_BY_APP}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public @ContextCreationFlags int getFlags() {
+ return mFlags;
+ }
+
+ /**
+ * Builder for {@link ContentCaptureContext} objects.
+ */
+ public static final class Builder {
+ private Bundle mExtras;
+ private Uri mUri;
+
+ /**
+ * Sets extra options associated with this context.
+ *
+ * <p>It can be used to provide vendor-specific data that can be modified and examined.
+ *
+ * @param extras extra options.
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ // TODO(b/111276913): check build just once / throw exception / test / document
+ mExtras = Preconditions.checkNotNull(extras);
+ return this;
+ }
+
+ /**
+ * Sets the {@link Uri} associated with this context.
+ *
+ * <p>See {@link View#setContentCaptureSession(ContentCaptureSession)} for an example.
+ *
+ * @param uri URI associated with this context.
+ * @return this builder.
+ */
+ @NonNull
+ public Builder setUri(@NonNull Uri uri) {
+ // TODO(b/111276913): check build just once / throw exception / test / document
+ mUri = Preconditions.checkNotNull(uri);
+ return this;
+ }
+
+ /**
+ * Builds the {@link ContentCaptureContext}.
+ */
+ public ContentCaptureContext build() {
+ // TODO(b/111276913): check build just once / throw exception / test / document
+ // TODO(b/111276913): make sure it at least one property (uri / extras) / test /
+ // throw exception / documment
+ return new ContentCaptureContext(this);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ // TODO(b/111276913): dump to proto as well
+ public void dump(PrintWriter pw) {
+ pw.print("comp="); pw.print(ComponentName.flattenToShortString(mComponentName));
+ pw.print(", taskId="); pw.print(mTaskId);
+ pw.print(", displayId="); pw.print(mDisplayId);
+ if (mFlags > 0) {
+ pw.print(", flags="); pw.print(mFlags);
+ }
+ if (mExtras != null) {
+ // NOTE: cannot dump because it could contain PII
+ pw.print(", hasExtras");
+ }
+ if (mUri != null) {
+ // NOTE: cannot dump because it could contain PII
+ pw.print(", hasUri");
+ }
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder("Context[act=")
+ .append(ComponentName.flattenToShortString(mComponentName))
+ .append(", taskId=").append(mTaskId)
+ .append(", displayId=").append(mDisplayId)
+ .append(", flags=").append(mFlags);
+ if (mExtras != null) {
+ // NOTE: cannot print because it could contain PII
+ builder.append(", hasExtras");
+ }
+ if (mUri != null) {
+ // NOTE: cannot print because it could contain PII
+ builder.append(", hasUri");
+ }
+ return builder.append(']').toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeInt(mHasClientContext ? 1 : 0);
+ if (mHasClientContext) {
+ parcel.writeParcelable(mUri, flags);
+ parcel.writeBundle(mExtras);
+ }
+ parcel.writeParcelable(mComponentName, flags);
+ if (mComponentName != null) {
+ parcel.writeInt(mTaskId);
+ parcel.writeInt(mDisplayId);
+ parcel.writeInt(mFlags);
+ }
+ }
+
+ public static final Parcelable.Creator<ContentCaptureContext> CREATOR =
+ new Parcelable.Creator<ContentCaptureContext>() {
+
+ @Override
+ public ContentCaptureContext createFromParcel(Parcel parcel) {
+ final boolean hasClientContext = parcel.readInt() == 1;
+
+ final ContentCaptureContext clientContext;
+ if (hasClientContext) {
+ final Builder builder = new Builder();
+ final Uri uri = parcel.readParcelable(null);
+ final Bundle extras = parcel.readBundle();
+ if (uri != null) builder.setUri(uri);
+ if (extras != null) builder.setExtras(extras);
+ // Must reconstruct the client context using the Builder API
+ clientContext = new ContentCaptureContext(builder);
+ } else {
+ clientContext = null;
+ }
+ final ComponentName componentName = parcel.readParcelable(null);
+ if (componentName == null) {
+ // Client-state only
+ return clientContext;
+ } else {
+ final int taskId = parcel.readInt();
+ final int displayId = parcel.readInt();
+ final int flags = parcel.readInt();
+ return new ContentCaptureContext(clientContext, componentName, taskId,
+ displayId, flags);
+ }
+ }
+
+ @Override
+ public ContentCaptureContext[] newArray(int size) {
+ return new ContentCaptureContext[size];
+ }
+ };
+}
diff --git a/core/java/android/view/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java
index 66fa530758e7..5d8fe5f51464 100644
--- a/core/java/android/view/contentcapture/ContentCaptureEvent.java
+++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java
@@ -21,7 +21,6 @@ import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
-import android.os.SystemClock;
import android.view.autofill.AutofillId;
import com.android.internal.util.Preconditions;
@@ -41,54 +40,18 @@ public final class ContentCaptureEvent implements Parcelable {
public static final int TYPE_ACTIVITY_CREATED = -1;
/**
- * Called when the activity is started.
- *
- * @deprecated - TODO(b/111276913): we should abstract the Activity lifecycle concepts into
- * something related to a session and/or domain.
- */
- @Deprecated
- public static final int TYPE_ACTIVITY_STARTED = 1;
-
- /**
- * Called when the activity is resumed.
- *
- * @deprecated - TODO(b/111276913): we should abstract the Activity lifecycle concepts into
- * something related to a session and/or domain.
- */
- @Deprecated
- public static final int TYPE_ACTIVITY_RESUMED = 2;
-
- /**
- * Called when the activity is paused.
- *
- * @deprecated - TODO(b/111276913): we should abstract the Activity lifecycle concepts into
- * something related to a session and/or domain.
- */
- @Deprecated
- public static final int TYPE_ACTIVITY_PAUSED = 3;
-
- /**
- * Called when the activity is stopped.
- *
- * @deprecated - TODO(b/111276913): we should abstract the Activity lifecycle concepts into
- * something related to a session and/or domain.
- */
- @Deprecated
- public static final int TYPE_ACTIVITY_STOPPED = 4;
-
- /**
* Called when a node has been added to the screen and is visible to the user.
*
* <p>The metadata of the node is available through {@link #getViewNode()}.
*/
- public static final int TYPE_VIEW_APPEARED = 5;
+ public static final int TYPE_VIEW_APPEARED = 1;
/**
* Called when a node has been removed from the screen and is not visible to the user anymore.
*
* <p>The id of the node is available through {@link #getId()}.
*/
- public static final int TYPE_VIEW_DISAPPEARED = 6;
+ public static final int TYPE_VIEW_DISAPPEARED = 2;
/**
* Called when the text of a node has been changed.
@@ -96,16 +59,12 @@ public final class ContentCaptureEvent implements Parcelable {
* <p>The id of the node is available through {@link #getId()}, and the new text is
* available through {@link #getText()}.
*/
- public static final int TYPE_VIEW_TEXT_CHANGED = 7;
+ public static final int TYPE_VIEW_TEXT_CHANGED = 3;
// TODO(b/111276913): add event to indicate when FLAG_SECURE was changed?
/** @hide */
@IntDef(prefix = { "TYPE_" }, value = {
- TYPE_ACTIVITY_STARTED,
- TYPE_ACTIVITY_PAUSED,
- TYPE_ACTIVITY_RESUMED,
- TYPE_ACTIVITY_STOPPED,
TYPE_VIEW_APPEARED,
TYPE_VIEW_DISAPPEARED,
TYPE_VIEW_TEXT_CHANGED
@@ -130,7 +89,7 @@ public final class ContentCaptureEvent implements Parcelable {
/** @hide */
public ContentCaptureEvent(int type, int flags) {
- this(type, SystemClock.uptimeMillis(), flags);
+ this(type, System.currentTimeMillis(), flags);
}
/** @hide */
@@ -159,9 +118,7 @@ public final class ContentCaptureEvent implements Parcelable {
/**
* Gets the type of the event.
*
- * @return one of {@link #TYPE_ACTIVITY_STARTED}, {@link #TYPE_ACTIVITY_RESUMED},
- * {@link #TYPE_ACTIVITY_PAUSED}, {@link #TYPE_ACTIVITY_STOPPED},
- * {@link #TYPE_VIEW_APPEARED}, {@link #TYPE_VIEW_DISAPPEARED},
+ * @return one of {@link #TYPE_VIEW_APPEARED}, {@link #TYPE_VIEW_DISAPPEARED},
* or {@link #TYPE_VIEW_TEXT_CHANGED}.
*/
public @EventType int getType() {
@@ -169,7 +126,7 @@ public final class ContentCaptureEvent implements Parcelable {
}
/**
- * Gets when the event was generated, in ms.
+ * Gets when the event was generated, in millis since epoch.
*/
public long getEventTime() {
return mEventTime;
@@ -179,7 +136,7 @@ public final class ContentCaptureEvent implements Parcelable {
* Gets optional flags associated with the event.
*
* @return either {@code 0} or
- * {@link android.view.contentcapture.ContentCaptureManager#FLAG_USER_INPUT}.
+ * {@link android.view.contentcapture.ContentCaptureSession#FLAG_USER_INPUT}.
*/
public int getFlags() {
return mFlags;
@@ -295,14 +252,6 @@ public final class ContentCaptureEvent implements Parcelable {
/** @hide */
public static String getTypeAsString(@EventType int type) {
switch (type) {
- case TYPE_ACTIVITY_STARTED:
- return "ACTIVITY_STARTED";
- case TYPE_ACTIVITY_RESUMED:
- return "ACTIVITY_RESUMED";
- case TYPE_ACTIVITY_PAUSED:
- return "ACTIVITY_PAUSED";
- case TYPE_ACTIVITY_STOPPED:
- return "ACTIVITY_STOPPED";
case TYPE_VIEW_APPEARED:
return "VIEW_APPEARED";
case TYPE_VIEW_DISAPPEARED:
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index cc0264ac0bc9..7fbbfb775397 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -15,36 +15,20 @@
*/
package android.view.contentcapture;
-import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED;
-import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED;
-import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED;
-
-import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
import android.content.ComponentName;
import android.content.Context;
-import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.SystemClock;
import android.util.Log;
-import android.util.TimeUtils;
import android.view.View;
-import android.view.ViewStructure;
-import android.view.autofill.AutofillId;
-import android.view.contentcapture.ContentCaptureEvent.EventType;
-import com.android.internal.os.IResultReceiver;
import com.android.internal.util.Preconditions;
import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
/*
@@ -62,63 +46,11 @@ public final class ContentCaptureManager {
private static final String TAG = ContentCaptureManager.class.getSimpleName();
- // TODO(b/111276913): define a way to dynamically set them(for example, using settings?)
- private static final boolean VERBOSE = false;
- private static final boolean DEBUG = true; // STOPSHIP if not set to false
-
- /**
- * Used to indicate that a text change was caused by user input (for example, through IME).
- */
- //TODO(b/111276913): link to notifyTextChanged() method once available
- public static final int FLAG_USER_INPUT = 0x1;
-
- /**
- * Initial state, when there is no session.
- *
- * @hide
- */
- public static final int STATE_UNKNOWN = 0;
-
- /**
- * Service's startSession() was called, but server didn't confirm it was created yet.
- *
- * @hide
- */
- public static final int STATE_WAITING_FOR_SERVER = 1;
-
- /**
- * Session is active.
- *
- * @hide
- */
- public static final int STATE_ACTIVE = 2;
-
- /**
- * Session is disabled.
- *
- * @hide
- */
- public static final int STATE_DISABLED = 3;
-
- /**
- * Handler message used to flush the buffer.
- */
- private static final int MSG_FLUSH = 1;
-
-
private static final String BG_THREAD_NAME = "intel_svc_streamer_thread";
- /**
- * Maximum number of events that are buffered before sent to the app.
- */
- // TODO(b/111276913): use settings
- private static final int MAX_BUFFER_SIZE = 100;
-
- /**
- * Frequency the buffer is flushed if stale.
- */
- // TODO(b/111276913): use settings
- private static final int FLUSHING_FREQUENCY_MS = 5_000;
+ // TODO(b/121044306): define a way to dynamically set them(for example, using settings?)
+ static final boolean VERBOSE = false;
+ static final boolean DEBUG = true; // STOPSHIP if not set to false
@NonNull
private final AtomicBoolean mDisabled = new AtomicBoolean();
@@ -129,29 +61,12 @@ public final class ContentCaptureManager {
@Nullable
private final IContentCaptureManager mService;
- @Nullable
- private String mId;
-
- private int mState = STATE_UNKNOWN;
-
- @Nullable
- private IBinder mApplicationToken;
-
- @Nullable
- private ComponentName mComponentName;
-
- /**
- * List of events held to be sent as a batch.
- */
- @Nullable
- private ArrayList<ContentCaptureEvent> mEvents;
-
- // TODO(b/111276913): use UI Thread directly (as calls are one-way) or a shared thread / handler
+ // TODO(b/119220549): use UI Thread directly (as calls are one-way) or a shared thread / handler
// held at the Application level
+ @NonNull
private final Handler mHandler;
- // Used just for debugging purposes (on dump)
- private long mNextFlush;
+ private ContentCaptureSession mMainSession;
/** @hide */
public ContentCaptureManager(@NonNull Context context,
@@ -161,290 +76,93 @@ public final class ContentCaptureManager {
Log.v(TAG, "Constructor for " + context.getPackageName());
}
mService = service;
- // TODO(b/111276913): use an existing bg thread instead...
+ // TODO(b/119220549): use an existing bg thread instead...
final HandlerThread bgThread = new HandlerThread(BG_THREAD_NAME);
bgThread.start();
mHandler = Handler.createAsync(bgThread.getLooper());
}
- /** @hide */
- public void onActivityCreated(@NonNull IBinder token, @NonNull ComponentName componentName) {
- if (!isContentCaptureEnabled()) return;
-
- mHandler.sendMessage(obtainMessage(ContentCaptureManager::handleStartSession, this,
- token, componentName));
- }
-
- private void handleStartSession(@NonNull IBinder token, @NonNull ComponentName componentName) {
- if (mState != STATE_UNKNOWN) {
- // TODO(b/111276913): revisit this scenario
- Log.w(TAG, "ignoring handleStartSession(" + token + ") while on state "
- + getStateAsString(mState));
- return;
- }
- mState = STATE_WAITING_FOR_SERVER;
- mId = UUID.randomUUID().toString();
- mApplicationToken = token;
- mComponentName = componentName;
-
- if (VERBOSE) {
- Log.v(TAG, "handleStartSession(): token=" + token + ", act="
- + getActivityDebugName() + ", id=" + mId);
- }
- final int flags = 0; // TODO(b/111276913): get proper flags
-
- try {
- mService.startSession(mContext.getUserId(), mApplicationToken, componentName,
- mId, flags, new IResultReceiver.Stub() {
- @Override
- public void send(int resultCode, Bundle resultData) {
- handleSessionStarted(resultCode);
- }
- });
- } catch (RemoteException e) {
- Log.w(TAG, "Error starting session for " + componentName.flattenToShortString() + ": "
- + e);
- }
- }
-
- private void handleSessionStarted(int resultCode) {
- mState = resultCode;
- mDisabled.set(mState == STATE_DISABLED);
- if (VERBOSE) {
- Log.v(TAG, "onActivityStarted() result: code=" + resultCode + ", id=" + mId
- + ", state=" + getStateAsString(mState) + ", disabled=" + mDisabled.get());
- }
- }
-
- private void handleSendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) {
- if (mEvents == null) {
- if (VERBOSE) {
- Log.v(TAG, "Creating buffer for " + MAX_BUFFER_SIZE + " events");
- }
- mEvents = new ArrayList<>(MAX_BUFFER_SIZE);
- }
- mEvents.add(event);
-
- final int numberEvents = mEvents.size();
-
- // TODO(b/120784831): need to optimize it so we buffer changes until a number of X are
- // buffered (either total or per autofillid). For
- // example, if the user typed "a", "b", "c" and the threshold is 3, we should buffer
- // "a" and "b" then send "abc".
- final boolean bufferEvent = numberEvents < MAX_BUFFER_SIZE;
-
- if (bufferEvent && !forceFlush) {
- handleScheduleFlush();
- return;
- }
-
- if (mState != STATE_ACTIVE) {
- // Callback from startSession hasn't been called yet - typically happens on system
- // apps that are started before the system service
- // TODO(b/111276913): try to ignore session while system is not ready / boot
- // not complete instead. Similarly, the manager service should return right away
- // when the user does not have a service set
- if (VERBOSE) {
- Log.v(TAG, "Closing session for " + getActivityDebugName()
- + " after " + numberEvents + " delayed events and state "
- + getStateAsString(mState));
- }
- handleResetState();
- // TODO(b/111276913): blacklist activity / use special flag to indicate that
- // when it's launched again
- return;
- }
-
- if (mId == null) {
- // Sanity check - should not happen
- Log.wtf(TAG, "null session id for " + getActivityDebugName());
- return;
- }
-
- handleForceFlush();
- }
-
- private void handleScheduleFlush() {
- if (mHandler.hasMessages(MSG_FLUSH)) {
- // "Renew" the flush message by removing the previous one
- mHandler.removeMessages(MSG_FLUSH);
- }
- mNextFlush = SystemClock.elapsedRealtime() + FLUSHING_FREQUENCY_MS;
- if (VERBOSE) {
- Log.v(TAG, "Scheduled to flush in " + FLUSHING_FREQUENCY_MS + "ms: " + mNextFlush);
- }
- mHandler.sendMessageDelayed(
- obtainMessage(ContentCaptureManager::handleFlushIfNeeded, this).setWhat(MSG_FLUSH),
- FLUSHING_FREQUENCY_MS);
- }
-
- private void handleFlushIfNeeded() {
- if (mEvents.isEmpty()) {
- if (VERBOSE) Log.v(TAG, "Nothing to flush");
- return;
- }
- handleForceFlush();
- }
-
- private void handleForceFlush() {
- final int numberEvents = mEvents.size();
- try {
- if (DEBUG) {
- Log.d(TAG, "Flushing " + numberEvents + " event(s) for " + getActivityDebugName());
- }
- mHandler.removeMessages(MSG_FLUSH);
- mService.sendEvents(mContext.getUserId(), mId, mEvents);
- // TODO(b/111276913): decide whether we should clear or set it to null, as each has
- // its own advantages: clearing will save extra allocations while the session is
- // active, while setting to null would save memory if there's no more event coming.
- mEvents.clear();
- } catch (RemoteException e) {
- Log.w(TAG, "Error sending " + numberEvents + " for " + getActivityDebugName()
- + ": " + e);
- }
+ @NonNull
+ private static Handler newHandler() {
+ // TODO(b/119220549): use an existing bg thread instead...
+ // TODO(b/119220549): use UI Thread directly (as calls are one-way) or an existing bgThread
+ // or a shared thread / handler held at the Application level
+ final HandlerThread bgThread = new HandlerThread(BG_THREAD_NAME);
+ bgThread.start();
+ return Handler.createAsync(bgThread.getLooper());
}
/**
- * Used for intermediate events (i.e, other than created and destroyed).
+ * Creates a new {@link ContentCaptureSession}.
*
- * @hide
+ * <p>See {@link View#setContentCaptureSession(ContentCaptureSession)} for more info.
*/
- public void onActivityLifecycleEvent(@EventType int type) {
- if (!isContentCaptureEnabled()) return;
- if (VERBOSE) {
- Log.v(TAG, "onActivityLifecycleEvent() for " + getActivityDebugName()
- + ": " + ContentCaptureEvent.getTypeAsString(type));
- }
- mHandler.sendMessage(obtainMessage(ContentCaptureManager::handleSendEvent, this,
- new ContentCaptureEvent(type), /* forceFlush= */ true));
- }
-
- /** @hide */
- public void onActivityDestroyed() {
- if (!isContentCaptureEnabled()) return;
-
- //TODO(b/111276913): check state (for example, how to handle if it's waiting for remote
- // id) and send it to the cache of batched commands
- if (VERBOSE) {
- Log.v(TAG, "onActivityDestroyed(): state=" + getStateAsString(mState)
- + ", mId=" + mId);
- }
-
- mHandler.sendMessage(obtainMessage(ContentCaptureManager::handleFinishSession, this));
- }
-
- private void handleFinishSession() {
- //TODO(b/111276913): right now both the ContentEvents and lifecycle sessions are sent
- // to system_server, so it's ok to call both in sequence here. But once we split
- // them so the events are sent directly to the service, we need to make sure they're
- // sent in order.
- try {
- if (DEBUG) {
- Log.d(TAG, "Finishing session " + mId + " with "
- + (mEvents == null ? 0 : mEvents.size()) + " event(s) for "
- + getActivityDebugName());
- }
-
- mService.finishSession(mContext.getUserId(), mId, mEvents);
- } catch (RemoteException e) {
- Log.e(TAG, "Error finishing session " + mId + " for " + getActivityDebugName()
- + ": " + e);
- } finally {
- handleResetState();
- }
- }
-
- private void handleResetState() {
- mState = STATE_UNKNOWN;
- mId = null;
- mApplicationToken = null;
- mComponentName = null;
- mEvents = null;
- mHandler.removeMessages(MSG_FLUSH);
- }
-
- /**
- * Notifies the Intelligence Service that a node has been added to the view structure.
+ @NonNull
+ public ContentCaptureSession createContentCaptureSession(
+ @NonNull ContentCaptureContext context) {
+ if (DEBUG) Log.d(TAG, "createContentCaptureSession(): " + context);
+ // TODO(b/121033016): for now we're updating the main session, but we need instead:
+ // 1.Keep a list of sessions
+ // 2.Making sure the applicationToken and componentName passed by
+ // the activity is used on all of these sessions
+ // 3.We might also need to delay the start of all of these sessions until
+ // onActivityStarted() is called (and the main session is created).
+ // 4.Close (and delete) these sessions when onActivityStopped() is called.
+ // 5.Figure out whether each session will have its own mDisabled AtomicBoolean.
+ if (mMainSession == null) {
+ mMainSession = new ContentCaptureSession(mContext, mHandler, mService,
+ mDisabled, Preconditions.checkNotNull(context));
+ } else {
+ throw new IllegalStateException("Manager already has a session: " + mMainSession);
+ }
+ return mMainSession;
+ }
+
+ /**
+ * Gets the main session associated with the context.
*
- * <p>Typically called "manually" by views that handle their own virtual view hierarchy, or
- * automatically by the Android System for views that return {@code true} on
- * {@link View#onProvideContentCaptureStructure(ViewStructure, int)}.
+ * <p>By default there's just one (associated with the activity lifecycle), but apps could
+ * explicitly add more using {@link #createContentCaptureSession(ContentCaptureContext)}.
*
- * @param node node that has been added.
+ * @hide
*/
- public void notifyViewAppeared(@NonNull ViewStructure node) {
- Preconditions.checkNotNull(node);
- if (!isContentCaptureEnabled()) return;
-
- if (!(node instanceof ViewNode.ViewStructureImpl)) {
- throw new IllegalArgumentException("Invalid node class: " + node.getClass());
+ @NonNull
+ public ContentCaptureSession getMainContentCaptureSession() {
+ // TODO(b/121033016): figure out how to manage the "default" session when it support
+ // multiple sessions (can't just be the first one, as it could be closed).
+ if (mMainSession == null) {
+ mMainSession = new ContentCaptureSession(mContext, mHandler, mService, mDisabled,
+ /* contentCaptureContext= */ null);
+ if (VERBOSE) {
+ Log.v(TAG, "getDefaultContentCaptureSession(): created " + mMainSession);
+ }
}
-
- mHandler.sendMessage(obtainMessage(ContentCaptureManager::handleSendEvent, this,
- new ContentCaptureEvent(TYPE_VIEW_APPEARED)
- .setViewNode(((ViewNode.ViewStructureImpl) node).mNode),
- /* forceFlush= */ false));
- }
-
- /**
- * Notifies the Intelligence Service that a node has been removed from the view structure.
- *
- * <p>Typically called "manually" by views that handle their own virtual view hierarchy, or
- * automatically by the Android System for standard views.
- *
- * @param id id of the node that has been removed.
- */
- public void notifyViewDisappeared(@NonNull AutofillId id) {
- Preconditions.checkNotNull(id);
- if (!isContentCaptureEnabled()) return;
-
- mHandler.sendMessage(obtainMessage(ContentCaptureManager::handleSendEvent, this,
- new ContentCaptureEvent(TYPE_VIEW_DISAPPEARED).setAutofillId(id),
- /* forceFlush= */ false));
+ return mMainSession;
}
- /**
- * Notifies the Intelligence Service that the value of a text node has been changed.
- *
- * @param id of the node.
- * @param text new text.
- * @param flags either {@code 0} or {@link #FLAG_USER_INPUT} when the value was explicitly
- * changed by the user (for example, through the keyboard).
- */
- public void notifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text,
- int flags) {
- Preconditions.checkNotNull(id);
-
- if (!isContentCaptureEnabled()) return;
-
- mHandler.sendMessage(obtainMessage(ContentCaptureManager::handleSendEvent, this,
- new ContentCaptureEvent(TYPE_VIEW_TEXT_CHANGED, flags).setAutofillId(id)
- .setText(text), /* forceFlush= */ false));
+ /** @hide */
+ public void onActivityStarted(@NonNull IBinder applicationToken,
+ @NonNull ComponentName activityComponent) {
+ // TODO(b/121033016): must start all sessions
+ getMainContentCaptureSession().start(applicationToken, activityComponent);
}
- /**
- * Creates a {@link ViewStructure} for a "standard" view.
- *
- * @hide
- */
- @NonNull
- public ViewStructure newViewStructure(@NonNull View view) {
- return new ViewNode.ViewStructureImpl(view);
+ /** @hide */
+ public void onActivityStopped() {
+ // TODO(b/121033016): must finish all sessions
+ getMainContentCaptureSession().destroy();
}
/**
- * Creates a {@link ViewStructure} for a "virtual" view, so it can be passed to
- * {@link #notifyViewAppeared(ViewStructure)} by the view managing the virtual view hierarchy.
+ * Flushes the content of all sessions.
*
- * @param parentId id of the virtual view parent (it can be obtained by calling
- * {@link ViewStructure#getAutofillId()} on the parent).
- * @param virtualId id of the virtual child, relative to the parent.
+ * <p>Typically called by {@code Activity} when it's paused / resumed.
*
- * @return a new {@link ViewStructure} that can be used for Content Capture purposes.
+ * @hide
*/
- @NonNull
- public ViewStructure newVirtualViewStructure(@NonNull AutofillId parentId, int virtualId) {
- return new ViewNode.ViewStructureImpl(parentId, virtualId);
+ public void flush() {
+ // TODO(b/121033016): must flush all sessions
+ getMainContentCaptureSession().flush();
}
/**
@@ -453,7 +171,7 @@ public final class ContentCaptureManager {
*/
@Nullable
public ComponentName getServiceComponentName() {
- //TODO(b/111276913): implement
+ //TODO(b/121047489): implement
return null;
}
@@ -471,71 +189,35 @@ public final class ContentCaptureManager {
* it on {@link android.app.Activity#onCreate(android.os.Bundle, android.os.PersistableBundle)}.
*/
public void setContentCaptureEnabled(boolean enabled) {
+ //TODO(b/111276913): implement (need to finish / disable all sessions)
+ }
+
+ /**
+ * Called by the ap to request the Content Capture service to remove user-data associated with
+ * some context.
+ *
+ * @param request object specifying what user data should be removed.
+ */
+ public void removeUserData(@NonNull UserDataRemovalRequest request) {
//TODO(b/111276913): implement
}
/** @hide */
public void dump(String prefix, PrintWriter pw) {
pw.print(prefix); pw.println("ContentCaptureManager");
- final String prefix2 = prefix + " ";
- pw.print(prefix2); pw.print("mContext: "); pw.println(mContext);
- pw.print(prefix2); pw.print("user: "); pw.println(mContext.getUserId());
- if (mService != null) {
- pw.print(prefix2); pw.print("mService: "); pw.println(mService);
- }
- pw.print(prefix2); pw.print("mDisabled: "); pw.println(mDisabled.get());
- pw.print(prefix2); pw.print("isEnabled(): "); pw.println(isContentCaptureEnabled());
- if (mId != null) {
- pw.print(prefix2); pw.print("id: "); pw.println(mId);
- }
- pw.print(prefix2); pw.print("state: "); pw.print(mState); pw.print(" (");
- pw.print(getStateAsString(mState)); pw.println(")");
- if (mApplicationToken != null) {
- pw.print(prefix2); pw.print("app token: "); pw.println(mApplicationToken);
- }
- if (mComponentName != null) {
- pw.print(prefix2); pw.print("component name: ");
- pw.println(mComponentName.flattenToShortString());
- }
- if (mEvents != null && !mEvents.isEmpty()) {
- final int numberEvents = mEvents.size();
- pw.print(prefix2); pw.print("buffered events: "); pw.print(numberEvents);
- pw.print('/'); pw.println(MAX_BUFFER_SIZE);
- if (VERBOSE && numberEvents > 0) {
- final String prefix3 = prefix2 + " ";
- for (int i = 0; i < numberEvents; i++) {
- final ContentCaptureEvent event = mEvents.get(i);
- pw.print(prefix3); pw.print(i); pw.print(": "); event.dump(pw);
- pw.println();
- }
- }
- pw.print(prefix2); pw.print("flush frequency: "); pw.println(FLUSHING_FREQUENCY_MS);
- pw.print(prefix2); pw.print("next flush: ");
- TimeUtils.formatDuration(mNextFlush - SystemClock.elapsedRealtime(), pw); pw.println();
- }
- }
-
- /**
- * Gets a string that can be used to identify the activity on logging statements.
- */
- private String getActivityDebugName() {
- return mComponentName == null ? mContext.getPackageName()
- : mComponentName.flattenToShortString();
- }
- @NonNull
- private static String getStateAsString(int state) {
- switch (state) {
- case STATE_UNKNOWN:
- return "UNKNOWN";
- case STATE_WAITING_FOR_SERVER:
- return "WAITING_FOR_SERVER";
- case STATE_ACTIVE:
- return "ACTIVE";
- case STATE_DISABLED:
- return "DISABLED";
- default:
- return "INVALID:" + state;
+ pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled.get());
+ pw.print(prefix); pw.print("Context: "); pw.println(mContext);
+ pw.print(prefix); pw.print("User: "); pw.println(mContext.getUserId());
+ if (mService != null) {
+ pw.print(prefix); pw.print("Service: "); pw.println(mService);
+ }
+ if (mMainSession != null) {
+ final String prefix2 = prefix + " ";
+ pw.print(prefix); pw.println("Main session:");
+ mMainSession.dump(prefix2, pw);
+ } else {
+ pw.print(prefix); pw.println("No sessions");
}
}
}
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
new file mode 100644
index 000000000000..632955d335cf
--- /dev/null
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -0,0 +1,570 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.contentcapture;
+
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED;
+import static android.view.contentcapture.ContentCaptureManager.DEBUG;
+import static android.view.contentcapture.ContentCaptureManager.VERBOSE;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.TimeUtils;
+import android.view.View;
+import android.view.ViewStructure;
+import android.view.autofill.AutofillId;
+
+import com.android.internal.os.IResultReceiver;
+import com.android.internal.util.Preconditions;
+
+import dalvik.system.CloseGuard;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Session used to notify a system-provided Content Capture service about events associated with
+ * views.
+ */
+public final class ContentCaptureSession implements AutoCloseable {
+
+ /*
+ * IMPLEMENTATION NOTICE:
+ *
+ * All methods in this class should return right away, or do the real work in a handler thread.
+ *
+ * Hence, the only field that must be thread-safe is mEnabled, which is called at the
+ * beginning of every method.
+ */
+
+ private static final String TAG = ContentCaptureSession.class.getSimpleName();
+
+ /**
+ * Used on {@link #notifyViewTextChanged(AutofillId, CharSequence, int)} to indicate that the
+ * thext change was caused by user input (for example, through IME).
+ */
+ public static final int FLAG_USER_INPUT = 0x1;
+
+ /**
+ * Initial state, when there is no session.
+ *
+ * @hide
+ */
+ public static final int STATE_UNKNOWN = 0;
+
+ /**
+ * Service's startSession() was called, but server didn't confirm it was created yet.
+ *
+ * @hide
+ */
+ public static final int STATE_WAITING_FOR_SERVER = 1;
+
+ /**
+ * Session is active.
+ *
+ * @hide
+ */
+ public static final int STATE_ACTIVE = 2;
+
+ /**
+ * Session is disabled.
+ *
+ * @hide
+ */
+ public static final int STATE_DISABLED = 3;
+
+ /**
+ * Handler message used to flush the buffer.
+ */
+ private static final int MSG_FLUSH = 1;
+
+ /**
+ * Maximum number of events that are buffered before sent to the app.
+ */
+ // TODO(b/121044064): use settings
+ private static final int MAX_BUFFER_SIZE = 100;
+
+ /**
+ * Frequency the buffer is flushed if stale.
+ */
+ // TODO(b/121044064): use settings
+ private static final int FLUSHING_FREQUENCY_MS = 5_000;
+
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ @NonNull
+ private final AtomicBoolean mDisabled;
+
+ @NonNull
+ private final Context mContext;
+
+ @NonNull
+ private final Handler mHandler;
+
+ @Nullable
+ private final IContentCaptureManager mService;
+
+ @Nullable
+ private final String mId = UUID.randomUUID().toString();
+
+ private int mState = STATE_UNKNOWN;
+
+ @Nullable
+ private IBinder mApplicationToken;
+
+ @Nullable
+ private ComponentName mComponentName;
+
+ /**
+ * List of events held to be sent as a batch.
+ */
+ // TODO(b/111276913): once we support multiple sessions, we need to move the buffer of events
+ // to its own class so it's shared by all sessions
+ @Nullable
+ private ArrayList<ContentCaptureEvent> mEvents;
+
+ // Used just for debugging purposes (on dump)
+ private long mNextFlush;
+
+ // Lazily created on demand.
+ private ContentCaptureSessionId mContentCaptureSessionId;
+
+ /**
+ * {@link ContentCaptureContext} set by client, or {@code null} when it's the
+ * {@link ContentCaptureManager#getMainContentCaptureSession() default session} for the
+ * context.
+ */
+ @Nullable
+ private final ContentCaptureContext mClientContext;
+
+ /** @hide */
+ protected ContentCaptureSession(@NonNull Context context, @NonNull Handler handler,
+ @Nullable IContentCaptureManager service, @NonNull AtomicBoolean disabled,
+ @Nullable ContentCaptureContext clientContext) {
+ mContext = context;
+ mHandler = handler;
+ mService = service;
+ mDisabled = disabled;
+ mClientContext = clientContext;
+ mCloseGuard.open("destroy");
+ }
+
+ /**
+ * Gets the id used to identify this session.
+ */
+ public ContentCaptureSessionId getContentCaptureSessionId() {
+ if (mContentCaptureSessionId == null) {
+ mContentCaptureSessionId = new ContentCaptureSessionId(mId);
+ }
+ return mContentCaptureSessionId;
+ }
+
+ /**
+ * Starts this session.
+ *
+ * @hide
+ */
+ void start(@NonNull IBinder applicationToken, @NonNull ComponentName activityComponent) {
+ if (!isContentCaptureEnabled()) return;
+
+ if (VERBOSE) {
+ Log.v(TAG, "start(): token=" + applicationToken + ", comp="
+ + ComponentName.flattenToShortString(activityComponent));
+ }
+
+ mHandler.sendMessage(obtainMessage(ContentCaptureSession::handleStartSession, this,
+ applicationToken, activityComponent));
+ }
+
+ /**
+ * Flushes the buffered events to the service.
+ *
+ * @hide
+ */
+ void flush() {
+ mHandler.sendMessage(obtainMessage(ContentCaptureSession::handleForceFlush, this));
+ }
+
+ /**
+ * Destroys this session, flushing out all pending notifications to the service.
+ *
+ * <p>Once destroyed, any new notification will be dropped.
+ */
+ public void destroy() {
+ //TODO(b/111276913): mark it as destroyed so other methods are ignored (and test on CTS)
+
+ if (!isContentCaptureEnabled()) return;
+
+ //TODO(b/111276913): check state (for example, how to handle if it's waiting for remote
+ // id) and send it to the cache of batched commands
+ if (VERBOSE) {
+ Log.v(TAG, "destroy(): state=" + getStateAsString(mState) + ", mId=" + mId);
+ }
+
+ mHandler.sendMessage(obtainMessage(ContentCaptureSession::handleDestroySession, this));
+ mCloseGuard.close();
+ }
+
+ /** @hide */
+ @Override
+ public void close() {
+ destroy();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+ destroy();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private void handleStartSession(@NonNull IBinder token, @NonNull ComponentName componentName) {
+ if (mState != STATE_UNKNOWN) {
+ // TODO(b/111276913): revisit this scenario
+ Log.w(TAG, "ignoring handleStartSession(" + token + ") while on state "
+ + getStateAsString(mState));
+ return;
+ }
+ mState = STATE_WAITING_FOR_SERVER;
+ mApplicationToken = token;
+ mComponentName = componentName;
+
+ if (VERBOSE) {
+ Log.v(TAG, "handleStartSession(): token=" + token + ", act="
+ + getActivityDebugName() + ", id=" + mId);
+ }
+ final int flags = 0; // TODO(b/111276913): get proper flags
+
+ try {
+ mService.startSession(mContext.getUserId(), mApplicationToken, componentName,
+ mId, mClientContext, flags, new IResultReceiver.Stub() {
+ @Override
+ public void send(int resultCode, Bundle resultData) {
+ handleSessionStarted(resultCode);
+ }
+ });
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error starting session for " + componentName.flattenToShortString() + ": "
+ + e);
+ }
+ }
+
+ private void handleSessionStarted(int resultCode) {
+ mState = resultCode;
+ mDisabled.set(mState == STATE_DISABLED);
+ if (VERBOSE) {
+ Log.v(TAG, "handleSessionStarted() result: code=" + resultCode + ", id=" + mId
+ + ", state=" + getStateAsString(mState) + ", disabled=" + mDisabled.get());
+ }
+ }
+
+ private void handleSendEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) {
+ if (mEvents == null) {
+ if (VERBOSE) {
+ Log.v(TAG, "Creating buffer for " + MAX_BUFFER_SIZE + " events");
+ }
+ mEvents = new ArrayList<>(MAX_BUFFER_SIZE);
+ }
+ mEvents.add(event);
+
+ final int numberEvents = mEvents.size();
+
+ // TODO(b/120784831): need to optimize it so we buffer changes until a number of X are
+ // buffered (either total or per autofillid). For
+ // example, if the user typed "a", "b", "c" and the threshold is 3, we should buffer
+ // "a" and "b" then send "abc".
+ final boolean bufferEvent = numberEvents < MAX_BUFFER_SIZE;
+
+ if (bufferEvent && !forceFlush) {
+ handleScheduleFlush();
+ return;
+ }
+
+ if (mState != STATE_ACTIVE) {
+ // Callback from startSession hasn't been called yet - typically happens on system
+ // apps that are started before the system service
+ // TODO(b/111276913): try to ignore session while system is not ready / boot
+ // not complete instead. Similarly, the manager service should return right away
+ // when the user does not have a service set
+ if (VERBOSE) {
+ Log.v(TAG, "Closing session for " + getActivityDebugName()
+ + " after " + numberEvents + " delayed events and state "
+ + getStateAsString(mState));
+ }
+ handleResetState();
+ // TODO(b/111276913): blacklist activity / use special flag to indicate that
+ // when it's launched again
+ return;
+ }
+
+ handleForceFlush();
+ }
+
+ private void handleScheduleFlush() {
+ if (mHandler.hasMessages(MSG_FLUSH)) {
+ // "Renew" the flush message by removing the previous one
+ mHandler.removeMessages(MSG_FLUSH);
+ }
+ mNextFlush = SystemClock.elapsedRealtime() + FLUSHING_FREQUENCY_MS;
+ if (VERBOSE) {
+ Log.v(TAG, "Scheduled to flush in " + FLUSHING_FREQUENCY_MS + "ms: " + mNextFlush);
+ }
+ mHandler.sendMessageDelayed(
+ obtainMessage(ContentCaptureSession::handleFlushIfNeeded, this).setWhat(MSG_FLUSH),
+ FLUSHING_FREQUENCY_MS);
+ }
+
+ private void handleFlushIfNeeded() {
+ if (mEvents.isEmpty()) {
+ if (VERBOSE) Log.v(TAG, "Nothing to flush");
+ return;
+ }
+ handleForceFlush();
+ }
+
+ private void handleForceFlush() {
+ if (mEvents == null) return;
+
+ final int numberEvents = mEvents.size();
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "Flushing " + numberEvents + " event(s) for " + getActivityDebugName());
+ }
+ mHandler.removeMessages(MSG_FLUSH);
+ mService.sendEvents(mContext.getUserId(), mId, mEvents);
+ // TODO(b/111276913): decide whether we should clear or set it to null, as each has
+ // its own advantages: clearing will save extra allocations while the session is
+ // active, while setting to null would save memory if there's no more event coming.
+ mEvents.clear();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error sending " + numberEvents + " for " + getActivityDebugName()
+ + ": " + e);
+ }
+ }
+
+ private void handleDestroySession() {
+ //TODO(b/111276913): right now both the ContentEvents and lifecycle sessions are sent
+ // to system_server, so it's ok to call both in sequence here. But once we split
+ // them so the events are sent directly to the service, we need to make sure they're
+ // sent in order.
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with "
+ + (mEvents == null ? 0 : mEvents.size()) + " event(s) for "
+ + getActivityDebugName());
+ }
+
+ mService.finishSession(mContext.getUserId(), mId, mEvents);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error destroying session " + mId + " for " + getActivityDebugName()
+ + ": " + e);
+ } finally {
+ handleResetState();
+ }
+ }
+
+ // TODO(b/111276913): once we support multiple sessions, we might need to move some of these
+ // clearings out.
+ private void handleResetState() {
+ mState = STATE_UNKNOWN;
+ mContentCaptureSessionId = null;
+ mApplicationToken = null;
+ mComponentName = null;
+ mEvents = null;
+ mHandler.removeMessages(MSG_FLUSH);
+ }
+
+ /**
+ * Notifies the Content Capture Service that a node has been added to the view structure.
+ *
+ * <p>Typically called "manually" by views that handle their own virtual view hierarchy, or
+ * automatically by the Android System for views that return {@code true} on
+ * {@link View#onProvideContentCaptureStructure(ViewStructure, int)}.
+ *
+ * @param node node that has been added.
+ */
+ public void notifyViewAppeared(@NonNull ViewStructure node) {
+ Preconditions.checkNotNull(node);
+ if (!isContentCaptureEnabled()) return;
+
+ if (!(node instanceof ViewNode.ViewStructureImpl)) {
+ throw new IllegalArgumentException("Invalid node class: " + node.getClass());
+ }
+
+ mHandler.sendMessage(obtainMessage(ContentCaptureSession::handleSendEvent, this,
+ new ContentCaptureEvent(TYPE_VIEW_APPEARED)
+ .setViewNode(((ViewNode.ViewStructureImpl) node).mNode),
+ /* forceFlush= */ false));
+ }
+
+ /**
+ * Notifies the Content Capture Service that a node has been removed from the view structure.
+ *
+ * <p>Typically called "manually" by views that handle their own virtual view hierarchy, or
+ * automatically by the Android System for standard views.
+ *
+ * @param id id of the node that has been removed.
+ */
+ public void notifyViewDisappeared(@NonNull AutofillId id) {
+ Preconditions.checkNotNull(id);
+ if (!isContentCaptureEnabled()) return;
+
+ mHandler.sendMessage(obtainMessage(ContentCaptureSession::handleSendEvent, this,
+ new ContentCaptureEvent(TYPE_VIEW_DISAPPEARED).setAutofillId(id),
+ /* forceFlush= */ false));
+ }
+
+ /**
+ * Notifies the Intelligence Service that the value of a text node has been changed.
+ *
+ * @param id of the node.
+ * @param text new text.
+ * @param flags either {@code 0} or {@link #FLAG_USER_INPUT} when the value was explicitly
+ * changed by the user (for example, through the keyboard).
+ */
+ public void notifyViewTextChanged(@NonNull AutofillId id, @Nullable CharSequence text,
+ int flags) {
+ Preconditions.checkNotNull(id);
+
+ if (!isContentCaptureEnabled()) return;
+
+ mHandler.sendMessage(obtainMessage(ContentCaptureSession::handleSendEvent, this,
+ new ContentCaptureEvent(TYPE_VIEW_TEXT_CHANGED, flags).setAutofillId(id)
+ .setText(text), /* forceFlush= */ false));
+ }
+
+ /**
+ * Creates a {@link ViewStructure} for a "standard" view.
+ *
+ * @hide
+ */
+ @NonNull
+ public ViewStructure newViewStructure(@NonNull View view) {
+ return new ViewNode.ViewStructureImpl(view);
+ }
+
+ /**
+ * Creates a {@link ViewStructure} for a "virtual" view, so it can be passed to
+ * {@link #notifyViewAppeared(ViewStructure)} by the view managing the virtual view hierarchy.
+ *
+ * @param parentId id of the virtual view parent (it can be obtained by calling
+ * {@link ViewStructure#getAutofillId()} on the parent).
+ * @param virtualId id of the virtual child, relative to the parent.
+ *
+ * @return a new {@link ViewStructure} that can be used for Content Capture purposes.
+ *
+ * @hide
+ */
+ @NonNull
+ public ViewStructure newVirtualViewStructure(@NonNull AutofillId parentId, int virtualId) {
+ return new ViewNode.ViewStructureImpl(parentId, virtualId);
+ }
+
+ private boolean isContentCaptureEnabled() {
+ return mService != null && !mDisabled.get();
+ }
+
+ void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
+ pw.print(prefix); pw.print("id: "); pw.println(mId);
+ pw.print(prefix); pw.print("mContext: "); pw.println(mContext);
+ pw.print(prefix); pw.print("user: "); pw.println(mContext.getUserId());
+ if (mService != null) {
+ pw.print(prefix); pw.print("mService: "); pw.println(mService);
+ }
+ if (mClientContext != null) {
+ // NOTE: we don't dump clientContent because it could have PII
+ pw.print(prefix); pw.println("hasClientContext");
+
+ }
+ pw.print(prefix); pw.print("mDisabled: "); pw.println(mDisabled.get());
+ pw.print(prefix); pw.print("isEnabled(): "); pw.println(isContentCaptureEnabled());
+ if (mContentCaptureSessionId != null) {
+ pw.print(prefix); pw.print("public id: "); pw.println(mContentCaptureSessionId);
+ }
+ pw.print(prefix); pw.print("state: "); pw.print(mState); pw.print(" (");
+ pw.print(getStateAsString(mState)); pw.println(")");
+ if (mApplicationToken != null) {
+ pw.print(prefix); pw.print("app token: "); pw.println(mApplicationToken);
+ }
+ if (mComponentName != null) {
+ pw.print(prefix); pw.print("component name: ");
+ pw.println(mComponentName.flattenToShortString());
+ }
+ if (mEvents != null && !mEvents.isEmpty()) {
+ final int numberEvents = mEvents.size();
+ pw.print(prefix); pw.print("buffered events: "); pw.print(numberEvents);
+ pw.print('/'); pw.println(MAX_BUFFER_SIZE);
+ if (VERBOSE && numberEvents > 0) {
+ final String prefix3 = prefix + " ";
+ for (int i = 0; i < numberEvents; i++) {
+ final ContentCaptureEvent event = mEvents.get(i);
+ pw.print(prefix3); pw.print(i); pw.print(": "); event.dump(pw);
+ pw.println();
+ }
+ }
+ pw.print(prefix); pw.print("flush frequency: "); pw.println(FLUSHING_FREQUENCY_MS);
+ pw.print(prefix); pw.print("next flush: ");
+ TimeUtils.formatDuration(mNextFlush - SystemClock.elapsedRealtime(), pw); pw.println();
+ }
+ }
+
+ /**
+ * Gets a string that can be used to identify the activity on logging statements.
+ */
+ private String getActivityDebugName() {
+ return mComponentName == null ? mContext.getPackageName()
+ : mComponentName.flattenToShortString();
+ }
+
+ @Override
+ public String toString() {
+ return mId;
+ }
+
+ @NonNull
+ private static String getStateAsString(int state) {
+ switch (state) {
+ case STATE_UNKNOWN:
+ return "UNKNOWN";
+ case STATE_WAITING_FOR_SERVER:
+ return "WAITING_FOR_SERVER";
+ case STATE_ACTIVE:
+ return "ACTIVE";
+ case STATE_DISABLED:
+ return "DISABLED";
+ default:
+ return "INVALID:" + state;
+ }
+ }
+}
diff --git a/core/java/android/service/contentcapture/InteractionSessionId.java b/core/java/android/view/contentcapture/ContentCaptureSessionId.java
index 84119472ca32..d7f9fcc4f7af 100644
--- a/core/java/android/service/contentcapture/InteractionSessionId.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSessionId.java
@@ -14,10 +14,9 @@
* limitations under the License.
*/
-package android.service.contentcapture;
+package android.view.contentcapture;
import android.annotation.NonNull;
-import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -25,11 +24,8 @@ import java.io.PrintWriter;
/**
* Identifier for a Content Capture session.
- *
- * @hide
*/
-@SystemApi
-public final class InteractionSessionId implements Parcelable {
+public final class ContentCaptureSessionId implements Parcelable {
private final @NonNull String mValue;
@@ -40,7 +36,7 @@ public final class InteractionSessionId implements Parcelable {
*
* @hide
*/
- public InteractionSessionId(@NonNull String value) {
+ public ContentCaptureSessionId(@NonNull String value) {
mValue = value;
}
@@ -64,7 +60,7 @@ public final class InteractionSessionId implements Parcelable {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
- final InteractionSessionId other = (InteractionSessionId) obj;
+ final ContentCaptureSessionId other = (ContentCaptureSessionId) obj;
if (mValue == null) {
if (other.mValue != null) return false;
} else if (!mValue.equals(other.mValue)) {
@@ -100,17 +96,17 @@ public final class InteractionSessionId implements Parcelable {
parcel.writeString(mValue);
}
- public static final Parcelable.Creator<InteractionSessionId> CREATOR =
- new Parcelable.Creator<InteractionSessionId>() {
+ public static final Parcelable.Creator<ContentCaptureSessionId> CREATOR =
+ new Parcelable.Creator<ContentCaptureSessionId>() {
@Override
- public InteractionSessionId createFromParcel(Parcel parcel) {
- return new InteractionSessionId(parcel.readString());
+ public ContentCaptureSessionId createFromParcel(Parcel parcel) {
+ return new ContentCaptureSessionId(parcel.readString());
}
@Override
- public InteractionSessionId[] newArray(int size) {
- return new InteractionSessionId[size];
+ public ContentCaptureSessionId[] newArray(int size) {
+ return new ContentCaptureSessionId[size];
}
};
}
diff --git a/core/java/android/view/contentcapture/IContentCaptureManager.aidl b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
index 8704dad2ea08..2002c5c1fc9a 100644
--- a/core/java/android/view/contentcapture/IContentCaptureManager.aidl
+++ b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
@@ -17,6 +17,7 @@
package android.view.contentcapture;
import android.content.ComponentName;
+import android.view.contentcapture.ContentCaptureContext;
import android.view.contentcapture.ContentCaptureEvent;
import android.os.IBinder;
@@ -29,7 +30,8 @@ import java.util.List;
*/
oneway interface IContentCaptureManager {
void startSession(int userId, IBinder activityToken, in ComponentName componentName,
- String sessionId, int flags, in IResultReceiver result);
+ String sessionId, in ContentCaptureContext clientContext, int flags,
+ in IResultReceiver result);
void finishSession(int userId, String sessionId, in List<ContentCaptureEvent> events);
void sendEvents(int userId, in String sessionId, in List<ContentCaptureEvent> events);
}
diff --git a/core/java/android/view/contentcapture/UserDataRemovalRequest.java b/core/java/android/view/contentcapture/UserDataRemovalRequest.java
new file mode 100644
index 000000000000..0261b70825a5
--- /dev/null
+++ b/core/java/android/view/contentcapture/UserDataRemovalRequest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view.contentcapture;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.List;
+
+/**
+ * Class used by apps to request the Content Capture service to remove user-data associated with
+ * some context.
+ */
+public final class UserDataRemovalRequest implements Parcelable {
+
+ private UserDataRemovalRequest(Builder builder) {
+ // TODO(b/111276913): implement
+ }
+
+ /**
+ * Gets the name of the app that's making the request.
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ public String getPackageName() {
+ // TODO(b/111276913): implement
+ // TODO(b/111276913): make sure it's set on system_service so it cannot be faked by app
+ return null;
+ }
+
+ /**
+ * Checks if app is requesting to remove all user data associated with its package.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean isForEverything() {
+ // TODO(b/111276913): implement
+ return false;
+ }
+
+ /**
+ * Gets the list of {@code Uri}s the apps is requesting to remove.
+ *
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ public List<UriRequest> getUriRequests() {
+ // TODO(b/111276913): implement
+ return null;
+ }
+
+ /**
+ * Builder for {@link UserDataRemovalRequest} objects.
+ */
+ public static final class Builder {
+
+ /**
+ * Requests servive to remove all user data associated with the app's package.
+ *
+ * @return this builder
+ */
+ @NonNull
+ public Builder forEverything() {
+ // TODO(b/111276913): implement
+ return this;
+ }
+
+ /**
+ * Request service to remove data associated with a given {@link Uri}.
+ *
+ * @param uri URI being requested to be removed.
+ * @param recursive whether it should remove the data associated with just the URI or its
+ * tree of descendants.
+ *
+ * @return this builder
+ */
+ public Builder addUri(@NonNull Uri uri, boolean recursive) {
+ // TODO(b/111276913): implement
+ return this;
+ }
+
+ /**
+ * Builds the {@link UserDataRemovalRequest}.
+ */
+ @NonNull
+ public UserDataRemovalRequest build() {
+ // TODO(b/111276913): implement / unit test / check built / document exceptions
+ return null;
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ // TODO(b/111276913): implement
+ }
+
+ public static final Parcelable.Creator<UserDataRemovalRequest> CREATOR =
+ new Parcelable.Creator<UserDataRemovalRequest>() {
+
+ @Override
+ public UserDataRemovalRequest createFromParcel(Parcel parcel) {
+ // TODO(b/111276913): implement
+ return null;
+ }
+
+ @Override
+ public UserDataRemovalRequest[] newArray(int size) {
+ return new UserDataRemovalRequest[size];
+ }
+ };
+
+ /**
+ * Representation of a request to remove data associated with an {@link Uri}.
+ * @hide
+ */
+ @SystemApi
+ public final class UriRequest {
+ private final @NonNull Uri mUri;
+ private final boolean mRecursive;
+
+ private UriRequest(@NonNull Uri uri, boolean recursive) {
+ this.mUri = uri;
+ this.mRecursive = recursive;
+ }
+
+ /**
+ * Gets the URI per se.
+ */
+ @NonNull
+ public Uri getUri() {
+ return mUri;
+ }
+
+ /**
+ * Checks whether the request is to remove just the data associated with the URI per se, or
+ * also its descendants.
+ */
+ @NonNull
+ public boolean isRecursive() {
+ return mRecursive;
+ }
+ }
+}
diff --git a/core/java/android/webkit/WebResourceResponse.java b/core/java/android/webkit/WebResourceResponse.java
index aae3056f6191..e66596b5c0e9 100644
--- a/core/java/android/webkit/WebResourceResponse.java
+++ b/core/java/android/webkit/WebResourceResponse.java
@@ -41,13 +41,21 @@ public class WebResourceResponse {
private InputStream mInputStream;
/**
- * Constructs a resource response with the given MIME type, encoding, and
- * input stream. Callers must implement
+ * Constructs a resource response with the given MIME type, character encoding,
+ * and input stream. Callers must implement
* {@link InputStream#read(byte[]) InputStream.read(byte[])} for the input
* stream.
*
- * @param mimeType the resource response's MIME type, for example text/html
- * @param encoding the resource response's encoding
+ * <p class="note"><b>Note:</b> The MIME type and character encoding must
+ * be specified as separate parameters (for example {@code "text/html"} and
+ * {@code "utf-8"}), not a single value like the {@code "text/html; charset=utf-8"}
+ * format used in the HTTP Content-Type header. Do not use the value of a HTTP
+ * Content-Encoding header for {@code encoding}, as that header does not specify a
+ * character encoding. Content without a defined character encoding (for example
+ * image resources) should pass {@code null} for {@code encoding}.
+ *
+ * @param mimeType the resource response's MIME type, for example {@code "text/html"}.
+ * @param encoding the resource response's character encoding, for example {@code "utf-8"}.
* @param data the input stream that provides the resource response's data. Must not be a
* StringBufferInputStream.
*/
@@ -63,8 +71,11 @@ public class WebResourceResponse {
* implement {@link InputStream#read(byte[]) InputStream.read(byte[])} for
* the input stream.
*
- * @param mimeType the resource response's MIME type, for example text/html
- * @param encoding the resource response's encoding
+ * <p class="note"><b>Note:</b> See {@link #WebResourceResponse(String,String,InputStream)}
+ * for details on what should be specified for {@code mimeType} and {@code encoding}.
+ *
+ * @param mimeType the resource response's MIME type, for example {@code "text/html"}.
+ * @param encoding the resource response's character encoding, for example {@code "utf-8"}.
* @param statusCode the status code needs to be in the ranges [100, 299], [400, 599].
* Causing a redirect by specifying a 3xx code is not supported.
* @param reasonPhrase the phrase describing the status code, for example "OK". Must be
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 90da81276ba3..b5cdedc4ea80 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -158,6 +158,7 @@ import android.view.animation.AnimationUtils;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
import android.view.contentcapture.ContentCaptureManager;
+import android.view.contentcapture.ContentCaptureSession;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo;
@@ -719,7 +720,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@ViewDebug.ExportedProperty(category = "text")
@UnsupportedAppUsage
private int mGravity = Gravity.TOP | Gravity.START;
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
private boolean mHorizontallyScrolling;
private int mAutoLinkMask;
@@ -5095,13 +5096,24 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
- * Returns whether the text is allowed to be wider than the View is.
+ * Returns whether the text is allowed to be wider than the View.
+ * If false, the text will be wrapped to the width of the View.
+ *
+ * @attr ref android.R.styleable#TextView_scrollHorizontally
+ * @see #setHorizontallyScrolling(boolean)
+ */
+ public final boolean isHorizontallyScrolling() {
+ return mHorizontallyScrolling;
+ }
+
+ /**
+ * Returns whether the text is allowed to be wider than the View.
* If false, the text will be wrapped to the width of the View.
*
* @attr ref android.R.styleable#TextView_scrollHorizontally
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public boolean getHorizontallyScrolling() {
return mHorizontallyScrolling;
}
@@ -10216,12 +10228,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
+ // TODO(b/121045053): should use a flag / boolean to keep status of SHOWN / HIDDEN instead
+ // of using isLaidout(), so it's not called in cases where it's laid out but a
+ // notifyAppeared was not sent.
+
// ContentCapture
if (isLaidOut() && isImportantForContentCapture() && isTextEditable()) {
final ContentCaptureManager cm = mContext.getSystemService(ContentCaptureManager.class);
if (cm != null && cm.isContentCaptureEnabled()) {
- // TODO(b/111276913): pass flags when edited by user / add CTS test
- cm.notifyViewTextChanged(getAutofillId(), getText(), /* flags= */ 0);
+ final ContentCaptureSession session = getContentCaptureSession();
+ if (session != null) {
+ // TODO(b/111276913): pass flags when edited by user / add CTS test
+ session.notifyViewTextChanged(getAutofillId(), getText(), /* flags= */ 0);
+ }
}
}
}
@@ -10812,6 +10831,25 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return onTextContextMenuItem(ID_PASTE);
}
break;
+ case KeyEvent.KEYCODE_INSERT:
+ if (canCopy()) {
+ return onTextContextMenuItem(ID_COPY);
+ }
+ break;
+ }
+ } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
+ // Handle Shift-only shortcuts.
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_FORWARD_DEL:
+ if (canCut()) {
+ return onTextContextMenuItem(ID_CUT);
+ }
+ break;
+ case KeyEvent.KEYCODE_INSERT:
+ if (canPaste()) {
+ return onTextContextMenuItem(ID_PASTE);
+ }
+ break;
}
} else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
// Handle Ctrl-Shift shortcuts.
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index 5465485790be..051a96c39f28 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -80,7 +80,8 @@ public class BinderCallsStats implements BinderInternal.Observer {
private final Queue<CallSession> mCallSessionsPool = new ConcurrentLinkedQueue<>();
private final Object mLock = new Object();
private final Random mRandom;
- private long mStartTime = System.currentTimeMillis();
+ private long mStartCurrentTime = System.currentTimeMillis();
+ private long mStartElapsedTime = SystemClock.elapsedRealtime();
private long mCallStatsCount = 0;
private boolean mAddDebugEntries = false;
@@ -329,8 +330,8 @@ public class BinderCallsStats implements BinderInternal.Observer {
// Debug entries added to help validate the data.
if (mAddDebugEntries && mBatteryStopwatch != null) {
- resultCallStats.add(createDebugEntry("start_time_millis", mStartTime));
- resultCallStats.add(createDebugEntry("end_time_millis", System.currentTimeMillis()));
+ resultCallStats.add(createDebugEntry("start_time_millis", mStartElapsedTime));
+ resultCallStats.add(createDebugEntry("end_time_millis", SystemClock.elapsedRealtime()));
resultCallStats.add(
createDebugEntry("battery_time_millis", mBatteryStopwatch.getMillis()));
}
@@ -370,7 +371,7 @@ public class BinderCallsStats implements BinderInternal.Observer {
long totalRecordedCallsCount = 0;
long totalCpuTime = 0;
pw.print("Start time: ");
- pw.println(DateFormat.format("yyyy-MM-dd HH:mm:ss", mStartTime));
+ pw.println(DateFormat.format("yyyy-MM-dd HH:mm:ss", mStartCurrentTime));
pw.print("On battery time (ms): ");
pw.println(mBatteryStopwatch != null ? mBatteryStopwatch.getMillis() : 0);
pw.println("Sampling interval period: " + mPeriodicSamplingInterval);
@@ -520,7 +521,8 @@ public class BinderCallsStats implements BinderInternal.Observer {
mCallStatsCount = 0;
mUidEntries.clear();
mExceptionCounts.clear();
- mStartTime = System.currentTimeMillis();
+ mStartCurrentTime = System.currentTimeMillis();
+ mStartElapsedTime = SystemClock.elapsedRealtime();
if (mBatteryStopwatch != null) {
mBatteryStopwatch.reset();
}
diff --git a/core/java/com/android/internal/os/LooperStats.java b/core/java/com/android/internal/os/LooperStats.java
index 01fd8ba58c74..9a7fb9f2717b 100644
--- a/core/java/com/android/internal/os/LooperStats.java
+++ b/core/java/com/android/internal/os/LooperStats.java
@@ -50,7 +50,8 @@ public class LooperStats implements Looper.Observer {
private int mSamplingInterval;
private CachedDeviceState.Readonly mDeviceState;
private CachedDeviceState.TimeInStateStopwatch mBatteryStopwatch;
- private long mStartTime = System.currentTimeMillis();
+ private long mStartCurrentTime = System.currentTimeMillis();
+ private long mStartElapsedTime = SystemClock.elapsedRealtime();
private boolean mAddDebugEntries = false;
public LooperStats(int samplingInterval, int entriesSizeCap) {
@@ -155,8 +156,8 @@ public class LooperStats implements Looper.Observer {
maybeAddSpecialEntry(exportedEntries, mHashCollisionEntry);
// Debug entries added to help validate the data.
if (mAddDebugEntries && mBatteryStopwatch != null) {
- exportedEntries.add(createDebugEntry("start_time_millis", mStartTime));
- exportedEntries.add(createDebugEntry("end_time_millis", System.currentTimeMillis()));
+ exportedEntries.add(createDebugEntry("start_time_millis", mStartElapsedTime));
+ exportedEntries.add(createDebugEntry("end_time_millis", SystemClock.elapsedRealtime()));
exportedEntries.add(
createDebugEntry("battery_time_millis", mBatteryStopwatch.getMillis()));
}
@@ -173,7 +174,11 @@ public class LooperStats implements Looper.Observer {
/** Returns a timestamp indicating when the statistics were last reset. */
public long getStartTimeMillis() {
- return mStartTime;
+ return mStartCurrentTime;
+ }
+
+ public long getStartElapsedTimeMillis() {
+ return mStartElapsedTime;
}
public long getBatteryTimeMillis() {
@@ -199,7 +204,8 @@ public class LooperStats implements Looper.Observer {
synchronized (mOverflowEntry) {
mOverflowEntry.reset();
}
- mStartTime = System.currentTimeMillis();
+ mStartCurrentTime = System.currentTimeMillis();
+ mStartElapsedTime = SystemClock.elapsedRealtime();
if (mBatteryStopwatch != null) {
mBatteryStopwatch.reset();
}
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index f669e94c1779..226b8db1540b 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -24,6 +24,7 @@ import dalvik.system.VMRuntime;
import libcore.util.EmptyArray;
+import java.io.File;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
@@ -42,6 +43,8 @@ public class ArrayUtils {
private static final int CACHE_SIZE = 73;
private static Object[] sCache = new Object[CACHE_SIZE];
+ public static final File[] EMPTY_FILE = new File[0];
+
private ArrayUtils() { /* cannot be instantiated */ }
public static byte[] newUnpaddedByteArray(int minLen) {
@@ -645,6 +648,10 @@ public class ArrayUtils {
return (val != null) ? val : EmptyArray.STRING;
}
+ public static @NonNull File[] defeatNullable(@Nullable File[] val) {
+ return (val != null) ? val : EMPTY_FILE;
+ }
+
/**
* Throws {@link ArrayIndexOutOfBoundsException} if the index is out of bounds.
*
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index c8834a889333..ae5c67df8f1e 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -26,9 +26,9 @@ import android.view.DisplayCutout;
import android.view.DragEvent;
import android.view.IWindow;
import android.view.IWindowSession;
-import android.view.PointerIcon;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
+import android.view.PointerIcon;
import com.android.internal.os.IResultReceiver;
@@ -76,7 +76,7 @@ public class BaseIWindow extends IWindow.Stub {
}
@Override
- public void windowFocusChanged(boolean hasFocus, boolean touchEnabled, boolean reportToClient) {
+ public void windowFocusChanged(boolean hasFocus, boolean touchEnabled) {
}
@Override
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index df24bc4d2abc..e97c9bc911dd 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -577,7 +577,7 @@ namespace PaintGlue {
}
}
- static SkScalar getMetricsInternal(jlong paintHandle, Paint::FontMetrics *metrics) {
+ static SkScalar getMetricsInternal(jlong paintHandle, SkFontMetrics *metrics) {
const int kElegantTop = 2500;
const int kElegantBottom = -1000;
const int kElegantAscent = 1900;
@@ -609,7 +609,7 @@ namespace PaintGlue {
}
static jfloat getFontMetrics(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj) {
- Paint::FontMetrics metrics;
+ SkFontMetrics metrics;
SkScalar spacing = getMetricsInternal(paintHandle, &metrics);
if (metricsObj) {
@@ -624,7 +624,7 @@ namespace PaintGlue {
}
static jint getFontMetricsInt(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj) {
- Paint::FontMetrics metrics;
+ SkFontMetrics metrics;
getMetricsInternal(paintHandle, &metrics);
int ascent = SkScalarRoundToInt(metrics.fAscent);
@@ -970,19 +970,19 @@ namespace PaintGlue {
}
static jfloat ascent(jlong paintHandle) {
- Paint::FontMetrics metrics;
+ SkFontMetrics metrics;
getMetricsInternal(paintHandle, &metrics);
return SkScalarToFloat(metrics.fAscent);
}
static jfloat descent(jlong paintHandle) {
- Paint::FontMetrics metrics;
+ SkFontMetrics metrics;
getMetricsInternal(paintHandle, &metrics);
return SkScalarToFloat(metrics.fDescent);
}
static jfloat getUnderlinePosition(jlong paintHandle) {
- Paint::FontMetrics metrics;
+ SkFontMetrics metrics;
getMetricsInternal(paintHandle, &metrics);
SkScalar position;
if (metrics.hasUnderlinePosition(&position)) {
@@ -994,7 +994,7 @@ namespace PaintGlue {
}
static jfloat getUnderlineThickness(jlong paintHandle) {
- Paint::FontMetrics metrics;
+ SkFontMetrics metrics;
getMetricsInternal(paintHandle, &metrics);
SkScalar thickness;
if (metrics.hasUnderlineThickness(&thickness)) {
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index a398e498a301..33b26899fe81 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -331,11 +331,13 @@ bool FileDescriptorInfo::ReopenOrDetach(std::string* error_msg) const {
return false;
}
- if (TEMP_FAILURE_RETRY(dup2(new_fd, fd)) == -1) {
+ int dupFlags = (fd_flags & FD_CLOEXEC) ? O_CLOEXEC : 0;
+ if (TEMP_FAILURE_RETRY(dup3(new_fd, fd, dupFlags)) == -1) {
close(new_fd);
- *error_msg = android::base::StringPrintf("Failed dup2(%d, %d) (%s): %s",
+ *error_msg = android::base::StringPrintf("Failed dup3(%d, %d, %d) (%s): %s",
fd,
new_fd,
+ dupFlags,
file_path.c_str(),
strerror(errno));
return false;
diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index 7fe3be870994..0ec8c1ada47e 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -220,6 +220,13 @@ message ConstantsProto {
// will use heartbeats, false will use a rolling window.
optional bool use_heartbeats = 23;
+ message TimeController {
+ // Whether or not TimeController should skip setting wakeup alarms for jobs that aren't
+ // ready now.
+ optional bool skip_not_ready_jobs = 1;
+ }
+ optional TimeController time_controller = 25;
+
message QuotaController {
// How much time each app will have to run jobs within their standby bucket window.
optional int64 allowed_time_per_period_ms = 1;
@@ -242,8 +249,12 @@ message ConstantsProto {
// expected to run only {@link QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS} within the past
// WINDOW_SIZE_MS.
optional int64 rare_window_size_ms = 6;
+ // The maximum amount of time an app can have its jobs running within a 24 hour window.
+ optional int64 max_execution_time_ms = 7;
}
optional QuotaController quota_controller = 24;
+
+ // Next tag: 26
}
message StateControllerProto {
diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
index c7a6b68fbc00..82460ec4ed8b 100644
--- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto
+++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
@@ -141,4 +141,7 @@ enum EventId {
PM_UNINSTALL = 113;
WIFI_SERVICE_ADD_NETWORK_SUGGESTIONS = 114;
WIFI_SERVICE_ADD_OR_UPDATE_NETWORK = 115;
+ QUERY_SUMMARY_FOR_DEVICE = 116;
+ REMOVE_CROSS_PROFILE_WIDGET_PROVIDER = 117;
+ ESTABLISH_VPN = 118;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index dca15bd49ca4..7fa3e66bae25 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4342,6 +4342,10 @@
@hide -->
<permission android:name="android.permission.AMBIENT_WALLPAPER"
android:protectionLevel="signature|preinstalled" />
+ <!-- @SystemApi Allows sensor privacy to be modified.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_SENSOR_PRIVACY"
+ android:protectionLevel="signature" />
<application android:process="system"
android:persistent="true"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 8dbea3896217..91faa5517860 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -7632,103 +7632,143 @@
<declare-styleable name="VoiceInteractionSession">
</declare-styleable>
+ <!-- {@deprecated Copy this definition into your own application project.} -->
<declare-styleable name="KeyboardView">
- <!-- Default KeyboardView style. -->
+ <!-- Default KeyboardView style.
+ {@deprecated Copy this definition into your own application project.} -->
<attr name="keyboardViewStyle" format="reference" />
<!-- Image for the key. This image needs to be a StateListDrawable, with the following
possible states: normal, pressed, checkable, checkable+pressed, checkable+checked,
- checkable+checked+pressed. -->
+ checkable+checked+pressed.
+ {@deprecated Copy this definition into your own application project.} -->
<attr name="keyBackground" format="reference" />
- <!-- Size of the text for character keys. -->
+ <!-- Size of the text for character keys.
+ {@deprecated Copy this definition into your own application project.} -->
<attr name="keyTextSize" format="dimension" />
- <!-- Size of the text for custom keys with some text and no icon. -->
+ <!-- Size of the text for custom keys with some text and no icon.
+ {@deprecated Copy this definition into your own application project.} -->
<attr name="labelTextSize" format="dimension" />
- <!-- Color to use for the label in a key. -->
+ <!-- Color to use for the label in a key.
+ {@deprecated Copy this definition into your own application project.} -->
<attr name="keyTextColor" format="color" />
- <!-- Layout resource for key press feedback.-->
+ <!-- Layout resource for key press feedback.
+ {@deprecated Copy this definition into your own application project.} -->
<attr name="keyPreviewLayout" format="reference" />
- <!-- Vertical offset of the key press feedback from the key. -->
+ <!-- Vertical offset of the key press feedback from the key.
+ {@deprecated Copy this definition into your own application project.} -->
<attr name="keyPreviewOffset" format="dimension" />
- <!-- Height of the key press feedback popup. -->
+ <!-- Height of the key press feedback popup.
+ {@deprecated Copy this definition into your own application project.} -->
<attr name="keyPreviewHeight" format="dimension" />
- <!-- Amount to offset the touch Y coordinate by, for bias correction. -->
+ <!-- Amount to offset the touch Y coordinate by, for bias correction.
+ {@deprecated Copy this definition into your own application project.} -->
<attr name="verticalCorrection" format="dimension" />
- <!-- Layout resource for popup keyboards. -->
+ <!-- Layout resource for popup keyboards.
+ {@deprecated Copy this definition into your own application project.} -->
<attr name="popupLayout" format="reference" />
+ <!-- {@deprecated Copy this definition into your own application project.} -->
<attr name="shadowColor" />
+ <!-- {@deprecated Copy this definition into your own application project.} -->
<attr name="shadowRadius" />
</declare-styleable>
+ <!-- {@deprecated Copy this definition into your own application project.} -->
<declare-styleable name="KeyboardViewPreviewState">
<!-- State for {@link android.inputmethodservice.KeyboardView KeyboardView}
- key preview background. -->
+ key preview background.
+ {@deprecated Copy this definition into your own application project.} -->
<attr name="state_long_pressable" format="boolean" />
</declare-styleable>
+ <!-- {@deprecated Copy this definition into your own application project.} -->
<declare-styleable name="Keyboard">
- <!-- Default width of a key, in pixels or percentage of display width. -->
+ <!-- Default width of a key, in pixels or percentage of display width.
+ {@deprecated Copy this definition into your own application project.} -->
<attr name="keyWidth" format="dimension|fraction" />
- <!-- Default height of a key, in pixels or percentage of display width. -->
+ <!-- Default height of a key, in pixels or percentage of display width.
+ {@deprecated Copy this definition into your own application project.} -->
<attr name="keyHeight" format="dimension|fraction" />
- <!-- Default horizontal gap between keys. -->
+ <!-- Default horizontal gap between keys.
+ {@deprecated Copy this definition into your own application project.} -->
<attr name="horizontalGap" format="dimension|fraction" />
- <!-- Default vertical gap between rows of keys. -->
+ <!-- Default vertical gap between rows of keys.
+ {@deprecated Copy this definition into your own application project.} -->
<attr name="verticalGap" format="dimension|fraction" />
</declare-styleable>
+ <!-- {@deprecated Copy this definition into your own application project.} -->
<declare-styleable name="Keyboard_Row">
- <!-- Row edge flags. -->
+ <!-- Row edge flags.
+ {@deprecated Copy this definition into your own application project.} -->
<attr name="rowEdgeFlags">
- <!-- Row is anchored to the top of the keyboard. -->
+ <!-- Row is anchored to the top of the keyboard.
+ {@deprecated Copy this definition into your own application project.} -->
<flag name="top" value="4" />
- <!-- Row is anchored to the bottom of the keyboard. -->
+ <!-- Row is anchored to the bottom of the keyboard.
+ {@deprecated Copy this definition into your own application project.} -->
<flag name="bottom" value="8" />
</attr>
<!-- Mode of the keyboard. If the mode doesn't match the
- requested keyboard mode, the row will be skipped. -->
+ requested keyboard mode, the row will be skipped.
+ {@deprecated Copy this definition into your own application project.} -->
<attr name="keyboardMode" format="reference" />
</declare-styleable>
+ <!-- {@deprecated Copy this definition into your own application project.} -->
<declare-styleable name="Keyboard_Key">
- <!-- The unicode value or comma-separated values that this key outputs. -->
+ <!-- The unicode value or comma-separated values that this key outputs.
+ {@deprecated Copy this definition into your own application project.} -->
<attr name="codes" format="integer|string" />
- <!-- The XML keyboard layout of any popup keyboard. -->
+ <!-- The XML keyboard layout of any popup keyboard.
+ {@deprecated Copy this definition into your own application project.} -->
<attr name="popupKeyboard" format="reference" />
- <!-- The characters to display in the popup keyboard. -->
+ <!-- The characters to display in the popup keyboard.
+ {@deprecated Copy this definition into your own application project.} -->
<attr name="popupCharacters" format="string" />
- <!-- Key edge flags. -->
+ <!-- Key edge flags.
+ {@deprecated Copy this definition into your own application project.} -->
<attr name="keyEdgeFlags">
- <!-- Key is anchored to the left of the keyboard. -->
+ <!-- Key is anchored to the left of the keyboard.
+ {@deprecated Copy this definition into your own application project.} -->
<flag name="left" value="1" />
- <!-- Key is anchored to the right of the keyboard. -->
+ <!-- Key is anchored to the right of the keyboard.
+ {@deprecated Copy this definition into your own application project.} -->
<flag name="right" value="2" />
</attr>
- <!-- Whether this is a modifier key such as Alt or Shift. -->
+ <!-- Whether this is a modifier key such as Alt or Shift.
+ {@deprecated Copy this definition into your own application project.} -->
<attr name="isModifier" format="boolean" />
- <!-- Whether this is a toggle key. -->
+ <!-- Whether this is a toggle key.
+ {@deprecated Copy this definition into your own application project.} -->
<attr name="isSticky" format="boolean" />
- <!-- Whether long-pressing on this key will make it repeat. -->
+ <!-- Whether long-pressing on this key will make it repeat.
+ {@deprecated Copy this definition into your own application project.} -->
<attr name="isRepeatable" format="boolean" />
- <!-- The icon to show in the popup preview. -->
+ <!-- The icon to show in the popup preview.
+ {@deprecated Copy this definition into your own application project.} -->
<attr name="iconPreview" format="reference" />
- <!-- The string of characters to output when this key is pressed. -->
+ <!-- The string of characters to output when this key is pressed.
+ {@deprecated Copy this definition into your own application project.} -->
<attr name="keyOutputText" format="string" />
- <!-- The label to display on the key. -->
+ <!-- The label to display on the key.
+ {@deprecated Copy this definition into your own application project.} -->
<attr name="keyLabel" format="string" />
- <!-- The icon to display on the key instead of the label. -->
+ <!-- The icon to display on the key instead of the label.
+ {@deprecated Copy this definition into your own application project.} -->
<attr name="keyIcon" format="reference" />
<!-- Mode of the keyboard. If the mode doesn't match the
- requested keyboard mode, the key will be skipped. -->
+ requested keyboard mode, the key will be skipped.
+ {@deprecated Copy this definition into your own application project.} -->
<attr name="keyboardMode" />
</declare-styleable>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 97a21a55f67f..9277daec7027 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1437,26 +1437,6 @@
<integer-array name="config_autoBrightnessKeyboardBacklightValues">
</integer-array>
- <!-- Array of hysteresis constraint values for brightening, represented as tenths of a
- percent. The length of this array is assumed to be one greater than
- config_dynamicHysteresisLuxLevels. The brightening threshold is calculated as
- lux * (1.0f + CONSTRAINT_VALUE). When the current lux is higher than this threshold,
- the screen brightness is recalculated. See the config_dynamicHysteresisLuxLevels
- description for how the constraint value is chosen. -->
- <integer-array name="config_dynamicHysteresisBrightLevels">
- <item>100</item>
- </integer-array>
-
- <!-- Array of hysteresis constraint values for darkening, represented as tenths of a
- percent. The length of this array is assumed to be one greater than
- config_dynamicHysteresisLuxLevels. The darkening threshold is calculated as
- lux * (1.0f - CONSTRAINT_VALUE). When the current lux is lower than this threshold,
- the screen brightness is recalculated. See the config_dynamicHysteresisLuxLevels
- description for how the constraint value is chosen. -->
- <integer-array name="config_dynamicHysteresisDarkLevels">
- <item>200</item>
- </integer-array>
-
<!-- An array describing the screen's backlight values corresponding to the brightness
values in the config_screenBrightnessNits array.
@@ -1474,19 +1454,73 @@
<array name="config_screenBrightnessNits">
</array>
-
<!-- Array of ambient lux threshold values. This is used for determining hysteresis constraint
values by calculating the index to use for lookup and then setting the constraint value
to the corresponding value of the array. The new brightening hysteresis constraint value
- is the n-th element of config_dynamicHysteresisBrightLevels, and the new darkening
- hysteresis constraint value is the n-th element of config_dynamicHysteresisDarkLevels.
+ is the n-th element of config_ambientBrighteningThresholds, and the new darkening
+ hysteresis constraint value is the n-th element of config_ambientDarkeningThresholds.
The (zero-based) index is calculated as follows: (MAX is the largest index of the array)
- condition calculated index
- value < lux[0] 0
- lux[n] <= value < lux[n+1] n+1
- lux[MAX] <= value MAX+1 -->
- <integer-array name="config_dynamicHysteresisLuxLevels">
+ condition calculated index
+ value < level[0] 0
+ level[n] <= value < level[n+1] n+1
+ level[MAX] <= value MAX+1 -->
+ <integer-array name="config_ambientThresholdLevels">
+ </integer-array>
+
+ <!-- Array of hysteresis constraint values for brightening, represented as tenths of a
+ percent. The length of this array is assumed to be one greater than
+ config_ambientThresholdLevels. The brightening threshold is calculated as
+ lux * (1.0f + CONSTRAINT_VALUE). When the current lux is higher than this threshold,
+ the screen brightness is recalculated. See the config_ambientThresholdLevels
+ description for how the constraint value is chosen. -->
+ <integer-array name="config_ambientBrighteningThresholds">
+ <item>100</item>
+ </integer-array>
+
+ <!-- Array of hysteresis constraint values for darkening, represented as tenths of a
+ percent. The length of this array is assumed to be one greater than
+ config_ambientThresholdLevels. The darkening threshold is calculated as
+ lux * (1.0f - CONSTRAINT_VALUE). When the current lux is lower than this threshold,
+ the screen brightness is recalculated. See the config_ambientThresholdLevels
+ description for how the constraint value is chosen. -->
+ <integer-array name="config_ambientDarkeningThresholds">
+ <item>200</item>
+ </integer-array>
+
+ <!-- Array of screen brightness threshold values. This is used for determining hysteresis
+ constraint values by calculating the index to use for lookup and then setting the
+ constraint value to the corresponding value of the array. The new brightening hysteresis
+ constraint value is the n-th element of config_screenBrighteningThresholds, and the new
+ darkening hysteresis constraint value is the n-th element of
+ config_screenDarkeningThresholds.
+
+ The (zero-based) index is calculated as follows: (MAX is the largest index of the array)
+ condition calculated index
+ value < level[0] 0
+ level[n] <= value < level[n+1] n+1
+ level[MAX] <= value MAX+1 -->
+ <integer-array name="config_screenThresholdLevels">
+ </integer-array>
+
+ <!-- Array of hysteresis constraint values for brightening, represented as tenths of a
+ percent. The length of this array is assumed to be one greater than
+ config_screenThresholdLevels. The brightening threshold is calculated as
+ screenBrightness * (1.0f + CONSTRAINT_VALUE). When the new screen brightness is higher
+ than this threshold, it is applied. See the config_screenThresholdLevels description for
+ how the constraint value is chosen. -->
+ <integer-array name="config_screenBrighteningThresholds">
+ <item>100</item>
+ </integer-array>
+
+ <!-- Array of hysteresis constraint values for darkening, represented as tenths of a
+ percent. The length of this array is assumed to be one greater than
+ config_screenThresholdLevels. The darkening threshold is calculated as
+ screenBrightness * (1.0f - CONSTRAINT_VALUE). When the new screen brightness is lower than
+ this threshold, it is applied. See the config_screenThresholdLevels description for how
+ the constraint value is chosen. -->
+ <integer-array name="config_screenDarkeningThresholds">
+ <item>200</item>
</integer-array>
<!-- Amount of time it takes for the light sensor to warm up in milliseconds.
@@ -2019,6 +2053,10 @@
This is intended to allow packaging drivers or tools for installation on a PC. -->
<string translatable="false" name="config_isoImagePath"></string>
+ <!-- Whether the system enables per-display focus. If the system has the input method for each
+ display, this value should be true. -->
+ <bool name="config_perDisplayFocusEnabled">false</bool>
+
<!-- Whether a software navigation bar should be shown. NOTE: in the future this may be
autodetected from the Configuration. -->
<bool name="config_showNavigationBar">false</bool>
@@ -3602,4 +3640,6 @@
(android.view.InputEventCompatProcessor). -->
<string name="config_inputEventCompatProcessorOverrideClassName" translatable="false"></string>
+ <!-- Component name for the default module metadata provider on this device -->
+ <string name="config_defaultModuleMetadataProvider">com.android.modulemetadata</string>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 1f9035e8b156..6f75d90c46c4 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1067,7 +1067,8 @@
<public type="id" name="inputExtractEditText" id="0x01020025" />
<!-- View ID of the {@link android.inputmethodservice.KeyboardView} within
- an input method's input area. -->
+ an input method's input area.
+ {@deprecated Use Copy this definition into your own application project.} -->
<public type="id" name="keyboardView" id="0x01020026" />
<!-- View ID of a {@link android.view.View} to close a popup keyboard -->
<public type="id" name="closeButton" id="0x01020027" />
@@ -1082,6 +1083,7 @@
<public type="style" name="Theme.InputMethod" id="0x01030054" />
<public type="style" name="Theme.NoDisplay" id="0x01030055" />
<public type="style" name="Animation.InputMethod" id="0x01030056" />
+ <!-- {@deprecated Use Copy this definition into your own application project.} -->
<public type="style" name="Widget.KeyboardView" id="0x01030057" />
<public type="style" name="ButtonBar" id="0x01030058" />
<public type="style" name="Theme.Panel" id="0x01030059" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index cab01f9c027f..f25427ad86fe 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3200,6 +3200,8 @@
<!-- [CHAR LIMIT=40] Title of dialog that is shown when system is starting. -->
<string name="android_start_title" product="default">Phone is starting\u2026</string>
<!-- [CHAR LIMIT=40] Title of dialog that is shown when system is starting. -->
+ <string name="android_start_title" product="automotive">Android is starting\u2026</string>
+ <!-- [CHAR LIMIT=40] Title of dialog that is shown when system is starting. -->
<string name="android_start_title" product="tablet">Tablet is starting\u2026</string>
<!-- [CHAR LIMIT=40] Title of dialog that is shown when system is starting. -->
<string name="android_start_title" product="device">Device is starting\u2026</string>
diff --git a/core/res/res/values/styles_device_defaults.xml b/core/res/res/values/styles_device_defaults.xml
index 2c04ec8e3d8b..4b97fe754fea 100644
--- a/core/res/res/values/styles_device_defaults.xml
+++ b/core/res/res/values/styles_device_defaults.xml
@@ -41,7 +41,9 @@ easier.
<item name="textAppearance">?attr/textAppearanceButton</item>
<item name="textColor">@color/btn_colored_text_material</item>
</style>
- <style name="Widget.DeviceDefault.TextView" parent="Widget.Material.TextView"/>
+ <style name="Widget.DeviceDefault.TextView" parent="Widget.Material.TextView">
+ <item name="textAppearance">@string/config_bodyFontFamily</item>
+ </style>
<style name="Widget.DeviceDefault.CheckedTextView" parent="Widget.Material.CheckedTextView"/>
<style name="Widget.DeviceDefault.AutoCompleteTextView" parent="Widget.Material.AutoCompleteTextView"/>
<style name="Widget.DeviceDefault.CompoundButton.CheckBox" parent="Widget.Material.CompoundButton.CheckBox"/>
@@ -266,6 +268,12 @@ easier.
<style name="TextAppearance.DeviceDefault.Notification.Reply" parent="TextAppearance.Material.Notification.Reply">
<item name="fontFamily">@string/config_bodyFontFamily</item>
</style>
+ <style name="TextAppearance.DeviceDefault.Notification.Info" parent="TextAppearance.Material.Notification.Info">
+ <item name="fontFamily">@string/config_bodyFontFamily</item>
+ </style>
+ <style name="TextAppearance.DeviceDefault.Notification.Info.Ambient" parent="TextAppearance.Material.Notification.Info.Ambient">
+ <item name="fontFamily">@string/config_bodyFontFamily</item>
+ </style>
<style name="TextAppearance.DeviceDefault.Widget" parent="TextAppearance.Material.Widget">
<item name="fontFamily">@string/config_bodyFontFamily</item>
</style>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 161e41681486..d68681d8c51d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1670,6 +1670,7 @@
<java-symbol type="bool" name="config_lockDayNightMode" />
<java-symbol type="bool" name="config_lockUiMode" />
<java-symbol type="bool" name="config_reverseDefaultRotation" />
+ <java-symbol type="bool" name="config_perDisplayFocusEnabled" />
<java-symbol type="bool" name="config_showNavigationBar" />
<java-symbol type="bool" name="config_supportAutoRotation" />
<java-symbol type="bool" name="target_honeycomb_needs_options_menu" />
@@ -1828,9 +1829,12 @@
<java-symbol type="array" name="config_autoBrightnessKeyboardBacklightValues" />
<java-symbol type="array" name="config_autoBrightnessLcdBacklightValues" />
<java-symbol type="array" name="config_autoBrightnessLevels" />
- <java-symbol type="array" name="config_dynamicHysteresisBrightLevels" />
- <java-symbol type="array" name="config_dynamicHysteresisDarkLevels" />
- <java-symbol type="array" name="config_dynamicHysteresisLuxLevels" />
+ <java-symbol type="array" name="config_ambientThresholdLevels" />
+ <java-symbol type="array" name="config_ambientBrighteningThresholds" />
+ <java-symbol type="array" name="config_ambientDarkeningThresholds" />
+ <java-symbol type="array" name="config_screenThresholdLevels" />
+ <java-symbol type="array" name="config_screenBrighteningThresholds" />
+ <java-symbol type="array" name="config_screenDarkeningThresholds" />
<java-symbol type="array" name="config_minimumBrightnessCurveLux" />
<java-symbol type="array" name="config_minimumBrightnessCurveNits" />
<java-symbol type="array" name="config_protectedNetworks" />
@@ -3515,4 +3519,6 @@
<java-symbol type="dimen" name="rounded_corner_radius" />
<java-symbol type="dimen" name="rounded_corner_radius_top" />
<java-symbol type="dimen" name="rounded_corner_radius_bottom" />
+
+ <java-symbol type="string" name="config_defaultModuleMetadataProvider" />
</resources>
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index c0c677a0b496..0ed821200ed4 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -211,7 +211,7 @@ easier.
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
<item name="colorError">@color/error_color_device_default_dark</item>
-
+ <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
</style>
<style name="Theme.DeviceDefault" parent="Theme.DeviceDefaultBase" />
@@ -936,6 +936,7 @@ easier.
<item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
<item name="colorAccent">@color/accent_device_default_light</item>
<item name="colorError">@color/error_color_device_default_light</item>
+ <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
</style>
<!-- Variant of the DeviceDefault (light) theme that has a solid (opaque) action bar with an
@@ -1705,9 +1706,11 @@ easier.
</style>
<style name="Theme.DeviceDefault.Notification" parent="@style/Theme.Material.Notification">
+ <item name="notificationHeaderTextAppearance">@style/TextAppearance.DeviceDefault.Notification.Info</item>
</style>
<style name="Theme.DeviceDefault.Notification.Ambient" parent="@style/Theme.Material.Notification.Ambient">
+ <item name="notificationHeaderTextAppearance">@style/TextAppearance.DeviceDefault.Notification.Info.Ambient</item>
</style>
</resources>
diff --git a/core/tests/coretests/src/android/os/OsTests.java b/core/tests/coretests/src/android/os/OsTests.java
index 985fa4f3cfc8..2b841269e5ae 100644
--- a/core/tests/coretests/src/android/os/OsTests.java
+++ b/core/tests/coretests/src/android/os/OsTests.java
@@ -33,7 +33,6 @@ public class OsTests {
suite.addTestSuite(MessageQueueTest.class);
suite.addTestSuite(MessengerTest.class);
suite.addTestSuite(PatternMatcherTest.class);
- suite.addTestSuite(SystemPropertiesTest.class);
return suite;
}
diff --git a/core/tests/coretests/src/android/os/SystemPropertiesTest.java b/core/tests/coretests/src/android/os/SystemPropertiesTest.java
deleted file mode 100644
index 25868ce0b702..000000000000
--- a/core/tests/coretests/src/android/os/SystemPropertiesTest.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os;
-
-import static junit.framework.Assert.assertEquals;
-import junit.framework.TestCase;
-
-import android.os.SystemProperties;
-import android.test.suitebuilder.annotation.SmallTest;
-
-public class SystemPropertiesTest extends TestCase {
- private static final String KEY = "com.android.frameworks.coretests";
- @SmallTest
- public void testProperties() throws Exception {
- if (false) {
- String value;
-
- SystemProperties.set(KEY, "");
- value = SystemProperties.get(KEY, "default");
- assertEquals("default", value);
-
- SystemProperties.set(KEY, "AAA");
- value = SystemProperties.get(KEY, "default");
- assertEquals("AAA", value);
-
- value = SystemProperties.get(KEY);
- assertEquals("AAA", value);
-
- SystemProperties.set(KEY, "");
- value = SystemProperties.get(KEY, "default");
- assertEquals("default", value);
-
- value = SystemProperties.get(KEY);
- assertEquals("", value);
- }
- }
-}
diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
new file mode 100644
index 000000000000..800b86418163
--- /dev/null
+++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider;
+
+import static android.provider.DeviceConfig.OnPropertyChangedListener;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.fail;
+
+import android.app.ActivityThread;
+import android.content.ContentResolver;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/** Tests that ensure appropriate settings are backed up. */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DeviceConfigTest {
+ // TODO(b/109919982): Migrate tests to CTS
+ private static final String sNamespace = "namespace1";
+ private static final String sKey = "key1";
+ private static final String sValue = "value1";
+ private static final long WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS = 2000; // 2 sec
+
+ private final Object mLock = new Object();
+
+ @After
+ public void cleanUp() {
+ deleteViaContentProvider(sNamespace, sKey);
+ }
+
+ @Test
+ public void getProperty_empty() {
+ String result = DeviceConfig.getProperty(sNamespace, sKey);
+ assertNull(result);
+ }
+
+ @Test
+ public void setAndGetProperty_sameNamespace() {
+ DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
+ String result = DeviceConfig.getProperty(sNamespace, sKey);
+ assertEquals(sValue, result);
+ }
+
+ @Test
+ public void setAndGetProperty_differentNamespace() {
+ String newNamespace = "namespace2";
+ DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
+ String result = DeviceConfig.getProperty(newNamespace, sKey);
+ assertNull(result);
+ }
+
+ @Test
+ public void setAndGetProperty_multipleNamespaces() {
+ String newNamespace = "namespace2";
+ String newValue = "value2";
+ DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
+ DeviceConfig.setProperty(newNamespace, sKey, newValue, false);
+ String result = DeviceConfig.getProperty(sNamespace, sKey);
+ assertEquals(sValue, result);
+ result = DeviceConfig.getProperty(newNamespace, sKey);
+ assertEquals(newValue, result);
+
+ // clean up
+ deleteViaContentProvider(newNamespace, sKey);
+ }
+
+ @Test
+ public void setAndGetProperty_overrideValue() {
+ String newValue = "value2";
+ DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
+ DeviceConfig.setProperty(sNamespace, sKey, newValue, false);
+ String result = DeviceConfig.getProperty(sNamespace, sKey);
+ assertEquals(newValue, result);
+ }
+
+ @Test
+ public void testListener() {
+ setPropertyAndAssertSuccessfulChange(sNamespace, sKey, sValue);
+ }
+
+ private void setPropertyAndAssertSuccessfulChange(String setNamespace, String setName,
+ String setValue) {
+ final AtomicBoolean success = new AtomicBoolean();
+
+ OnPropertyChangedListener changeListener = new OnPropertyChangedListener() {
+ @Override
+ public void onPropertyChanged(String namespace, String name, String value) {
+ assertEquals(setNamespace, namespace);
+ assertEquals(setName, name);
+ assertEquals(setValue, value);
+ success.set(true);
+
+ synchronized (mLock) {
+ mLock.notifyAll();
+ }
+ }
+ };
+ Executor executor = ActivityThread.currentApplication().getMainExecutor();
+ DeviceConfig.addOnPropertyChangedListener(setNamespace, executor, changeListener);
+ try {
+ DeviceConfig.setProperty(setNamespace, setName, setValue, false);
+
+ final long startTimeMillis = SystemClock.uptimeMillis();
+ synchronized (mLock) {
+ while (true) {
+ if (success.get()) {
+ return;
+ }
+ final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+ if (elapsedTimeMillis >= WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS) {
+ fail("Could not change setting for "
+ + WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS + " ms");
+ }
+ final long remainingTimeMillis = WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS
+ - elapsedTimeMillis;
+ try {
+ mLock.wait(remainingTimeMillis);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+ }
+ } finally {
+ DeviceConfig.removeOnPropertyChangedListener(changeListener);
+ }
+ }
+
+ private static boolean deleteViaContentProvider(String namespace, String key) {
+ ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver();
+ String compositeName = namespace + "/" + key;
+ Bundle result = resolver.call(
+ DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, compositeName, null);
+ assertNotNull(result);
+ return compositeName.equals(result.getString(Settings.NameValueTable.VALUE));
+ }
+
+}
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 3a37fb627b26..6d1aae12d858 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -543,7 +543,9 @@ public class SettingsBackupTest {
Settings.Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED,
Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS,
Settings.Global.BACKUP_AGENT_TIMEOUT_PARAMETERS,
- Settings.Global.BACKUP_MULTI_USER_ENABLED);
+ Settings.Global.BACKUP_MULTI_USER_ENABLED,
+ Settings.Global.ISOLATED_STORAGE_LOCAL,
+ Settings.Global.ISOLATED_STORAGE_REMOTE);
private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS =
newHashSet(
Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
@@ -584,6 +586,7 @@ public class SettingsBackupTest {
Settings.Secure.DEFAULT_INPUT_METHOD,
Settings.Secure.DEVICE_PAIRED,
Settings.Secure.DIALER_DEFAULT_APPLICATION,
+ Settings.Secure.DISABLE_AIRPLANE_MODE_AFTER_SP_DISABLED,
Settings.Secure.DISABLED_PRINT_SERVICES,
Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS,
Settings.Secure.DISPLAY_DENSITY_FORCED,
@@ -605,6 +608,8 @@ public class SettingsBackupTest {
Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT, // Candidate?
Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
Settings.Secure.LOCK_TO_APP_EXIT_LOCKED,
+ Settings.Secure.MAINTAIN_AIRPLANE_MODE_AFTER_SP_DISABLED,
+ Settings.Secure.MAINTAIN_LOCATION_AFTER_SP_DISABLED,
Settings.Secure.MANAGED_PROFILE_CONTACT_REMOTE_SEARCH,
Settings.Secure.MULTI_PRESS_TIMEOUT,
Settings.Secure.NFC_PAYMENT_FOREGROUND,
@@ -616,6 +621,7 @@ public class SettingsBackupTest {
Settings.Secure.PARENTAL_CONTROL_LAST_UPDATE,
Settings.Secure.PAYMENT_SERVICE_SEARCH_URI,
Settings.Secure.PRINT_SERVICE_SEARCH_URI,
+ Settings.Secure.REENABLE_LOCATION_AFTER_SP_DISABLED,
Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT, // Candidate?
Settings.Secure.SEARCH_GLOBAL_SEARCH_ACTIVITY,
Settings.Secure.SEARCH_MAX_RESULTS_PER_SOURCE,
@@ -639,6 +645,7 @@ public class SettingsBackupTest {
Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE,
Settings.Secure.SELECTED_SPELL_CHECKER, // Intentionally removed in Q
Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE, // Intentionally removed in Q
+ Settings.Secure.SENSOR_PRIVACY_SENSOR_STATE,
Settings.Secure.SETTINGS_CLASSNAME,
Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, // candidate?
Settings.Secure.SHOW_ROTATION_SUGGESTIONS,
diff --git a/core/tests/coretests/src/android/provider/SettingsProviderTest.java b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
index 04e880225b96..cb6f0e692082 100644
--- a/core/tests/coretests/src/android/provider/SettingsProviderTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
@@ -44,12 +44,6 @@ import java.util.Map;
/** Unit test for SettingsProvider. */
public class SettingsProviderTest extends AndroidTestCase {
- /**
- * TODO(b/113100523): Move this to DeviceConfig.java when it is added, and expose it as a System
- * API.
- */
- private static final Uri CONFIG_CONTENT_URI =
- Uri.parse("content://" + Settings.AUTHORITY + "/config");
@MediumTest
public void testNameValueCache() {
@@ -406,27 +400,27 @@ public class SettingsProviderTest extends AndroidTestCase {
try {
// value is empty
Bundle results =
- r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+ r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
assertNull(results.get(Settings.NameValueTable.VALUE));
// save value
- results = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+ results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
assertNull(results);
// value is no longer empty
- results = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+ results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
assertEquals(value, results.get(Settings.NameValueTable.VALUE));
// save new value
args.putString(Settings.NameValueTable.VALUE, newValue);
- r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+ r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
// new value is returned
- results = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+ results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
assertEquals(newValue, results.get(Settings.NameValueTable.VALUE));
} finally {
// clean up
- r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+ r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
}
}
@@ -440,22 +434,23 @@ public class SettingsProviderTest extends AndroidTestCase {
try {
// save value
- r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+ r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
// get value
Bundle results =
- r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+ r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
assertEquals(value, results.get(Settings.NameValueTable.VALUE));
// delete value
- results = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+ results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name,
+ null);
// value is empty now
- results = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+ results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
assertNull(results.get(Settings.NameValueTable.VALUE));
} finally {
// clean up
- r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+ r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
}
}
@@ -473,12 +468,12 @@ public class SettingsProviderTest extends AndroidTestCase {
try {
// save both values
- r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+ r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
args.putString(Settings.NameValueTable.VALUE, newValue);
- r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, newName, args);
+ r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, newName, args);
// list all values
- Bundle result = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_LIST_CONFIG,
+ Bundle result = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_LIST_CONFIG,
null, null);
Map<String, String> keyValueMap =
(HashMap) result.getSerializable(Settings.NameValueTable.VALUE);
@@ -488,14 +483,14 @@ public class SettingsProviderTest extends AndroidTestCase {
// list values for prefix
args.putString(Settings.CALL_METHOD_PREFIX_KEY, prefix);
- result = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_LIST_CONFIG, null, args);
+ result = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_LIST_CONFIG, null, args);
keyValueMap = (HashMap) result.getSerializable(Settings.NameValueTable.VALUE);
assertThat(keyValueMap, aMapWithSize(1));
assertEquals(value, keyValueMap.get(name));
} finally {
// clean up
- r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
- r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, newName, null);
+ r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+ r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, newName, null);
}
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
index 97f02cbc27e9..dc3a12f0bbf5 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
@@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.os.Binder;
+import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -637,7 +638,7 @@ public class BinderCallsStatsTest {
@Test
public void testAddsDebugEntries() {
- long startTime = System.currentTimeMillis();
+ long startTime = SystemClock.elapsedRealtime();
TestBinderCallsStats bcs = new TestBinderCallsStats();
bcs.setAddDebugEntries(true);
ArrayList<BinderCallsStats.ExportedCallStat> callStats = bcs.getExportedCallStats();
diff --git a/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java b/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java
index f26dfad0b037..b65c1e6210a7 100644
--- a/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java
@@ -442,11 +442,13 @@ public final class LooperStatsTest {
LooperStats.ExportedEntry debugEntry1 = entries.get(1);
assertThat(debugEntry1.handlerClassName).isEqualTo("");
assertThat(debugEntry1.messageName).isEqualTo("__DEBUG_start_time_millis");
- assertThat(debugEntry1.totalLatencyMicros).isEqualTo(looperStats.getStartTimeMillis());
+ assertThat(debugEntry1.totalLatencyMicros).isEqualTo(
+ looperStats.getStartElapsedTimeMillis());
LooperStats.ExportedEntry debugEntry2 = entries.get(2);
assertThat(debugEntry2.handlerClassName).isEqualTo("");
assertThat(debugEntry2.messageName).isEqualTo("__DEBUG_end_time_millis");
- assertThat(debugEntry2.totalLatencyMicros).isAtLeast(looperStats.getStartTimeMillis());
+ assertThat(debugEntry2.totalLatencyMicros).isAtLeast(
+ looperStats.getStartElapsedTimeMillis());
LooperStats.ExportedEntry debugEntry3 = entries.get(3);
assertThat(debugEntry3.handlerClassName).isEqualTo("");
assertThat(debugEntry3.messageName).isEqualTo("__DEBUG_battery_time_millis");
diff --git a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
index 933e54e840c5..928351e7de8c 100644
--- a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
+++ b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
@@ -16,13 +16,13 @@
package android.os;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
+import android.test.suitebuilder.annotation.SmallTest;
import junit.framework.TestCase;
-import android.os.SystemProperties;
-import android.test.suitebuilder.annotation.SmallTest;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
public class SystemPropertiesTest extends TestCase {
private static final String KEY = "sys.testkey";
@@ -188,4 +188,25 @@ public class SystemPropertiesTest extends TestCase {
fail("InterruptedException");
}
}
+
+ @SmallTest
+ public void testDigestOf() {
+ final String empty = SystemProperties.digestOf();
+ final String finger = SystemProperties.digestOf("ro.build.fingerprint");
+ final String fingerBrand = SystemProperties.digestOf(
+ "ro.build.fingerprint", "ro.product.brand");
+ final String brandFinger = SystemProperties.digestOf(
+ "ro.product.brand", "ro.build.fingerprint");
+
+ // Shouldn't change over time
+ assertTrue(Objects.equals(finger, SystemProperties.digestOf("ro.build.fingerprint")));
+
+ // Different properties means different results
+ assertFalse(Objects.equals(empty, finger));
+ assertFalse(Objects.equals(empty, fingerBrand));
+ assertFalse(Objects.equals(finger, fingerBrand));
+
+ // Same properties means same result
+ assertTrue(Objects.equals(fingerBrand, brandFinger));
+ }
}
diff --git a/data/etc/Android.mk b/data/etc/Android.mk
index d24c140ad19a..61ef426f510a 100644
--- a/data/etc/Android.mk
+++ b/data/etc/Android.mk
@@ -50,6 +50,25 @@ include $(BUILD_PREBUILT)
########################
include $(CLEAR_VARS)
+LOCAL_MODULE := privapp_whitelist_com.android.settings
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_RELATIVE_PATH := permissions
+LOCAL_MODULE_STEM := com.android.settings.xml
+LOCAL_SRC_FILES := com.android.settings.xml
+include $(BUILD_PREBUILT)
+
+########################
+include $(CLEAR_VARS)
+LOCAL_MODULE := privapp_whitelist_com.android.systemui
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_RELATIVE_PATH := permissions
+LOCAL_MODULE_STEM := com.android.systemui.xml
+LOCAL_SRC_FILES := com.android.systemui.xml
+include $(BUILD_PREBUILT)
+
+
+########################
+include $(CLEAR_VARS)
LOCAL_MODULE := com.android.timezone.updater.xml
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_RELATIVE_PATH := permissions
diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml
new file mode 100644
index 000000000000..2110a8fa7e3d
--- /dev/null
+++ b/data/etc/com.android.settings.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<permissions>
+ <privapp-permissions package="com.android.settings">
+ <permission name="android.permission.ACCESS_CHECKIN_PROPERTIES"/>
+ <permission name="android.permission.ACCESS_NOTIFICATIONS"/>
+ <permission name="android.permission.BACKUP"/>
+ <permission name="android.permission.BATTERY_STATS"/>
+ <permission name="android.permission.BLUETOOTH_PRIVILEGED"/>
+ <permission name="android.permission.CHANGE_APP_IDLE_STATE"/>
+ <permission name="android.permission.CHANGE_CONFIGURATION"/>
+ <permission name="android.permission.DELETE_PACKAGES"/>
+ <permission name="android.permission.FORCE_STOP_PACKAGES"/>
+ <permission name="android.permission.LOCAL_MAC_ADDRESS"/>
+ <permission name="android.permission.MANAGE_DEBUGGING"/>
+ <permission name="android.permission.MANAGE_DEVICE_ADMINS"/>
+ <permission name="android.permission.MANAGE_FINGERPRINT"/>
+ <permission name="android.permission.MANAGE_USB"/>
+ <permission name="android.permission.MANAGE_USERS"/>
+ <permission name="android.permission.MANAGE_USER_OEM_UNLOCK_STATE" />
+ <permission name="android.permission.MASTER_CLEAR"/>
+ <permission name="android.permission.MODIFY_PHONE_STATE"/>
+ <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
+ <permission name="android.permission.MOVE_PACKAGE"/>
+ <permission name="android.permission.OVERRIDE_WIFI_CONFIG"/>
+ <permission name="android.permission.PACKAGE_USAGE_STATS"/>
+ <permission name="android.permission.READ_SEARCH_INDEXABLES"/>
+ <permission name="android.permission.REBOOT"/>
+ <permission name="android.permission.SET_TIME"/>
+ <permission name="android.permission.STATUS_BAR"/>
+ <permission name="android.permission.TETHER_PRIVILEGED"/>
+ <permission name="android.permission.USE_RESERVED_DISK"/>
+ <permission name="android.permission.USER_ACTIVITY"/>
+ <permission name="android.permission.WRITE_APN_SETTINGS"/>
+ <permission name="android.permission.WRITE_MEDIA_STORAGE"/>
+ <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+ <permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
+ </privapp-permissions>
+</permissions>
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
new file mode 100644
index 000000000000..b65bc1d4d9d4
--- /dev/null
+++ b/data/etc/com.android.systemui.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<permissions>
+ <privapp-permissions package="com.android.systemui">
+ <permission name="android.permission.BATTERY_STATS"/>
+ <permission name="android.permission.BIND_APPWIDGET"/>
+ <permission name="android.permission.BLUETOOTH_PRIVILEGED"/>
+ <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
+ <permission name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"/>
+ <permission name="android.permission.CHANGE_OVERLAY_PACKAGES"/>
+ <permission name="android.permission.CONNECTIVITY_INTERNAL"/>
+ <permission name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"/>
+ <permission name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"/>
+ <permission name="android.permission.CONTROL_VPN"/>
+ <permission name="android.permission.DUMP"/>
+ <permission name="android.permission.GET_APP_OPS_STATS"/>
+ <permission name="android.permission.INTERACT_ACROSS_USERS"/>
+ <permission name="android.permission.MANAGE_ACTIVITY_STACKS"/>
+ <permission name="android.permission.MANAGE_DEBUGGING"/>
+ <permission name="android.permission.MANAGE_SENSOR_PRIVACY"/>
+ <permission name="android.permission.MANAGE_USB"/>
+ <permission name="android.permission.MANAGE_USERS"/>
+ <permission name="android.permission.MASTER_CLEAR"/>
+ <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
+ <permission name="android.permission.MODIFY_PHONE_STATE"/>
+ <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
+ <permission name="android.permission.OVERRIDE_WIFI_CONFIG"/>
+ <permission name="android.permission.READ_DREAM_STATE"/>
+ <permission name="android.permission.READ_FRAME_BUFFER"/>
+ <permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
+ <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
+ <permission name="android.permission.REAL_GET_TASKS"/>
+ <permission name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE"/>
+ <permission name="android.permission.START_ACTIVITY_AS_CALLER"/>
+ <permission name="android.permission.START_TASKS_FROM_RECENTS"/>
+ <permission name="android.permission.STATUS_BAR"/>
+ <permission name="android.permission.STOP_APP_SWITCHES"/>
+ <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
+ <permission name="android.permission.TETHER_PRIVILEGED"/>
+ <permission name="android.permission.UPDATE_APP_OPS_STATS"/>
+ <permission name="android.permission.USE_RESERVED_DISK"/>
+ <permission name="android.permission.WRITE_DREAM_STATE"/>
+ <permission name="android.permission.WRITE_MEDIA_STORAGE"/>
+ <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+ <permission name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"/>
+ <permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
+ </privapp-permissions>
+</permissions>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index dcf95fdbb438..af570b39925b 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -250,42 +250,6 @@ applications that come with the platform
<permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
</privapp-permissions>
- <privapp-permissions package="com.android.settings">
- <permission name="android.permission.ACCESS_CHECKIN_PROPERTIES"/>
- <permission name="android.permission.ACCESS_NOTIFICATIONS"/>
- <permission name="android.permission.BACKUP"/>
- <permission name="android.permission.BATTERY_STATS"/>
- <permission name="android.permission.BLUETOOTH_PRIVILEGED"/>
- <permission name="android.permission.CHANGE_APP_IDLE_STATE"/>
- <permission name="android.permission.CHANGE_CONFIGURATION"/>
- <permission name="android.permission.DELETE_PACKAGES"/>
- <permission name="android.permission.FORCE_STOP_PACKAGES"/>
- <permission name="android.permission.LOCAL_MAC_ADDRESS"/>
- <permission name="android.permission.MANAGE_DEBUGGING"/>
- <permission name="android.permission.MANAGE_DEVICE_ADMINS"/>
- <permission name="android.permission.MANAGE_FINGERPRINT"/>
- <permission name="android.permission.MANAGE_USB"/>
- <permission name="android.permission.MANAGE_USERS"/>
- <permission name="android.permission.MANAGE_USER_OEM_UNLOCK_STATE" />
- <permission name="android.permission.MASTER_CLEAR"/>
- <permission name="android.permission.MODIFY_PHONE_STATE"/>
- <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
- <permission name="android.permission.MOVE_PACKAGE"/>
- <permission name="android.permission.OVERRIDE_WIFI_CONFIG"/>
- <permission name="android.permission.PACKAGE_USAGE_STATS"/>
- <permission name="android.permission.READ_SEARCH_INDEXABLES"/>
- <permission name="android.permission.REBOOT"/>
- <permission name="android.permission.SET_TIME"/>
- <permission name="android.permission.STATUS_BAR"/>
- <permission name="android.permission.TETHER_PRIVILEGED"/>
- <permission name="android.permission.USE_RESERVED_DISK"/>
- <permission name="android.permission.USER_ACTIVITY"/>
- <permission name="android.permission.WRITE_APN_SETTINGS"/>
- <permission name="android.permission.WRITE_MEDIA_STORAGE"/>
- <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
- <permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
- </privapp-permissions>
-
<privapp-permissions package="com.android.settings.intelligence">
<permission name="android.permission.MANAGE_FINGERPRINT"/>
<permission name="android.permission.MODIFY_PHONE_STATE"/>
@@ -370,50 +334,6 @@ applications that come with the platform
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
</privapp-permissions>
- <privapp-permissions package="com.android.systemui">
- <permission name="android.permission.BATTERY_STATS"/>
- <permission name="android.permission.BIND_APPWIDGET"/>
- <permission name="android.permission.BLUETOOTH_PRIVILEGED"/>
- <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
- <permission name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"/>
- <permission name="android.permission.CHANGE_OVERLAY_PACKAGES"/>
- <permission name="android.permission.CONNECTIVITY_INTERNAL"/>
- <permission name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"/>
- <permission name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"/>
- <permission name="android.permission.CONTROL_VPN"/>
- <permission name="android.permission.DUMP"/>
- <permission name="android.permission.GET_APP_OPS_STATS"/>
- <permission name="android.permission.INTERACT_ACROSS_USERS"/>
- <permission name="android.permission.MANAGE_ACTIVITY_STACKS"/>
- <permission name="android.permission.MANAGE_DEBUGGING"/>
- <permission name="android.permission.MANAGE_USB"/>
- <permission name="android.permission.MANAGE_USERS"/>
- <permission name="android.permission.MASTER_CLEAR"/>
- <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
- <permission name="android.permission.MODIFY_PHONE_STATE"/>
- <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
- <permission name="android.permission.OVERRIDE_WIFI_CONFIG"/>
- <permission name="android.permission.READ_DREAM_STATE"/>
- <permission name="android.permission.READ_FRAME_BUFFER"/>
- <permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
- <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
- <permission name="android.permission.REAL_GET_TASKS"/>
- <permission name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE"/>
- <permission name="android.permission.START_ACTIVITY_AS_CALLER"/>
- <permission name="android.permission.START_TASKS_FROM_RECENTS"/>
- <permission name="android.permission.STATUS_BAR"/>
- <permission name="android.permission.STOP_APP_SWITCHES"/>
- <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
- <permission name="android.permission.TETHER_PRIVILEGED"/>
- <permission name="android.permission.UPDATE_APP_OPS_STATS"/>
- <permission name="android.permission.USE_RESERVED_DISK"/>
- <permission name="android.permission.WRITE_DREAM_STATE"/>
- <permission name="android.permission.WRITE_MEDIA_STORAGE"/>
- <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
- <permission name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"/>
- <permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
- </privapp-permissions>
-
<privapp-permissions package="com.android.tv">
<permission name="android.permission.CHANGE_HDMI_CEC_ACTIVE_SOURCE"/>
<permission name="android.permission.DVB_DEVICE"/>
diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp
index 0b9d82b105a3..ed167e57158e 100644
--- a/libs/hwui/DeviceInfo.cpp
+++ b/libs/hwui/DeviceInfo.cpp
@@ -20,6 +20,7 @@
#include <gui/ISurfaceComposer.h>
#include <gui/SurfaceComposerClient.h>
+#include <ui/GraphicTypes.h>
#include <mutex>
#include <thread>
@@ -61,6 +62,50 @@ DisplayInfo QueryDisplayInfo() {
return displayInfo;
}
+static void queryWideColorGamutPreference(SkColorSpace::Gamut* colorGamut,
+ sk_sp<SkColorSpace>* colorSpace, SkColorType* colorType) {
+ if (Properties::isolatedProcess) {
+ *colorGamut = SkColorSpace::Gamut::kSRGB_Gamut;
+ *colorSpace = SkColorSpace::MakeSRGB();
+ *colorType = SkColorType::kN32_SkColorType;
+ return;
+ }
+ ui::Dataspace defaultDataspace, wcgDataspace;
+ ui::PixelFormat defaultPixelFormat, wcgPixelFormat;
+ status_t status =
+ SurfaceComposerClient::getCompositionPreference(&defaultDataspace, &defaultPixelFormat,
+ &wcgDataspace, &wcgPixelFormat);
+ LOG_ALWAYS_FATAL_IF(status, "Failed to get composition preference, error %d", status);
+ switch (wcgDataspace) {
+ case ui::Dataspace::DISPLAY_P3:
+ *colorGamut = SkColorSpace::Gamut::kDCIP3_D65_Gamut;
+ *colorSpace = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
+ SkColorSpace::Gamut::kDCIP3_D65_Gamut);
+ break;
+ case ui::Dataspace::V0_SCRGB:
+ *colorGamut = SkColorSpace::Gamut::kSRGB_Gamut;
+ *colorSpace = SkColorSpace::MakeSRGB();
+ break;
+ case ui::Dataspace::V0_SRGB:
+ // when sRGB is returned, it means wide color gamut is not supported.
+ *colorGamut = SkColorSpace::Gamut::kSRGB_Gamut;
+ *colorSpace = SkColorSpace::MakeSRGB();
+ break;
+ default:
+ LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space.");
+ }
+ switch (wcgPixelFormat) {
+ case ui::PixelFormat::RGBA_8888:
+ *colorType = SkColorType::kN32_SkColorType;
+ break;
+ case ui::PixelFormat::RGBA_FP16:
+ *colorType = SkColorType::kRGBA_F16_SkColorType;
+ break;
+ default:
+ LOG_ALWAYS_FATAL("Unreachable: unsupported pixel format.");
+ }
+}
+
DeviceInfo::DeviceInfo() {
#if HWUI_NULL_GPU
mMaxTextureSize = NULL_GPU_MAX_TEXTURE_SIZE;
@@ -68,6 +113,7 @@ DeviceInfo::DeviceInfo() {
mMaxTextureSize = -1;
#endif
mDisplayInfo = QueryDisplayInfo();
+ queryWideColorGamutPreference(&mWideColorGamut, &mWideColorSpace, &mWideColorType);
}
int DeviceInfo::maxTextureSize() const {
diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h
index 595621573e6e..9bcc8e8a3dbe 100644
--- a/libs/hwui/DeviceInfo.h
+++ b/libs/hwui/DeviceInfo.h
@@ -16,6 +16,7 @@
#ifndef DEVICEINFO_H
#define DEVICEINFO_H
+#include <SkImageInfo.h>
#include <ui/DisplayInfo.h>
#include "utils/Macros.h"
@@ -37,6 +38,9 @@ public:
// context or if you are using the HWUI_NULL_GPU
int maxTextureSize() const;
const DisplayInfo& displayInfo() const { return mDisplayInfo; }
+ SkColorSpace::Gamut getWideColorGamut() const { return mWideColorGamut; }
+ sk_sp<SkColorSpace> getWideColorSpace() const { return mWideColorSpace; }
+ SkColorType getWideColorType() const { return mWideColorType; }
private:
friend class renderthread::RenderThread;
@@ -46,6 +50,9 @@ private:
int mMaxTextureSize;
DisplayInfo mDisplayInfo;
+ SkColorSpace::Gamut mWideColorGamut;
+ sk_sp<SkColorSpace> mWideColorSpace;
+ SkColorType mWideColorType;
};
} /* namespace uirenderer */
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index e2ea2bc37375..a09da6bf359e 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -52,7 +52,7 @@ void Canvas::drawTextDecorations(float x, float y, float length, const SkPaint&
const SkScalar left = x;
const SkScalar right = x + length;
if (flags & SkPaint::kUnderlineText_ReserveFlag) {
- Paint::FontMetrics metrics;
+ SkFontMetrics metrics;
paint.getFontMetrics(&metrics);
SkScalar position;
if (!metrics.hasUnderlinePosition(&position)) {
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 07979a22c988..4338b1cc2a21 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -162,22 +162,17 @@ bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBeh
mEglSurface = EGL_NO_SURFACE;
}
+ setSurfaceColorProperties(colorMode);
+
if (surface) {
mRenderThread.requireGlContext();
- auto newSurface = mEglManager.createSurface(surface, colorMode);
+ auto newSurface = mEglManager.createSurface(surface, colorMode, mSurfaceColorGamut);
if (!newSurface) {
return false;
}
mEglSurface = newSurface.unwrap();
}
- if (colorMode == ColorMode::SRGB) {
- mSurfaceColorType = SkColorType::kN32_SkColorType;
- } else if (colorMode == ColorMode::WideColorGamut) {
- mSurfaceColorType = SkColorType::kRGBA_F16_SkColorType;
- }
- mSurfaceColorSpace = SkColorSpace::MakeSRGB();
-
if (mEglSurface != EGL_NO_SURFACE) {
const bool preserveBuffer = (swapBehavior != SwapBehavior::kSwap_discardBuffer);
mBufferPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer);
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 7a255c15bf5f..7f62ab5abb7d 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -17,6 +17,7 @@
#include "SkiaPipeline.h"
#include <SkImageEncoder.h>
+#include <SkImageInfo.h>
#include <SkImagePriv.h>
#include <SkOverdrawCanvas.h>
#include <SkOverdrawColorFilter.h>
@@ -453,6 +454,20 @@ void SkiaPipeline::dumpResourceCacheUsage() const {
ALOGD("%s", log.c_str());
}
+void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) {
+ if (colorMode == ColorMode::SRGB) {
+ mSurfaceColorType = SkColorType::kN32_SkColorType;
+ mSurfaceColorGamut = SkColorSpace::Gamut::kSRGB_Gamut;
+ mSurfaceColorSpace = SkColorSpace::MakeSRGB();
+ } else if (colorMode == ColorMode::WideColorGamut) {
+ mSurfaceColorType = DeviceInfo::get()->getWideColorType();
+ mSurfaceColorGamut = DeviceInfo::get()->getWideColorGamut();
+ mSurfaceColorSpace = DeviceInfo::get()->getWideColorSpace();
+ } else {
+ LOG_ALWAYS_FATAL("Unreachable: unsupported color mode.");
+ }
+}
+
// Overdraw debugging
// These colors should be kept in sync with Caches::getOverdrawColor() with a few differences.
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index 42a411a6808c..af58f634ecd0 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -107,9 +107,11 @@ public:
protected:
void dumpResourceCacheUsage() const;
+ void setSurfaceColorProperties(renderthread::ColorMode colorMode);
renderthread::RenderThread& mRenderThread;
SkColorType mSurfaceColorType;
+ SkColorSpace::Gamut mSurfaceColorGamut;
sk_sp<SkColorSpace> mSurfaceColorSpace;
private:
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index 437b5dc83f58..65ae0ddeccf3 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -122,15 +122,10 @@ bool SkiaVulkanPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBeh
mVkSurface = nullptr;
}
- mSurfaceColorSpace = SkColorSpace::MakeSRGB();
+ setSurfaceColorProperties(colorMode);
if (surface) {
- mVkSurface = mVkManager.createSurface(surface, colorMode, mSurfaceColorSpace);
- }
-
- if (colorMode == ColorMode::SRGB) {
- mSurfaceColorType = SkColorType::kN32_SkColorType;
- } else if (colorMode == ColorMode::WideColorGamut) {
- mSurfaceColorType = SkColorType::kRGBA_F16_SkColorType;
+ mVkSurface = mVkManager.createSurface(surface, colorMode, mSurfaceColorSpace,
+ mSurfaceColorGamut, mSurfaceColorType);
}
return mVkSurface != nullptr;
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 8230dfd44f9a..56eedff4a6e6 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -89,7 +89,8 @@ EglManager::EglManager()
, mEglConfigWideGamut(nullptr)
, mEglContext(EGL_NO_CONTEXT)
, mPBufferSurface(EGL_NO_SURFACE)
- , mCurrentSurface(EGL_NO_SURFACE) {}
+ , mCurrentSurface(EGL_NO_SURFACE)
+ , mHasWideColorGamutSupport(false) {}
EglManager::~EglManager() {
destroy();
@@ -128,6 +129,81 @@ void EglManager::initialize() {
createContext();
createPBufferSurface();
makeCurrent(mPBufferSurface, nullptr, /* force */ true);
+
+ SkColorSpace::Gamut wideColorGamut = DeviceInfo::get()->getWideColorGamut();
+ bool hasWideColorSpaceExtension = false;
+ if (wideColorGamut == SkColorSpace::Gamut::kDCIP3_D65_Gamut) {
+ hasWideColorSpaceExtension = EglExtensions.displayP3;
+ } else if (wideColorGamut == SkColorSpace::Gamut::kSRGB_Gamut) {
+ hasWideColorSpaceExtension = EglExtensions.scRGB;
+ } else {
+ LOG_ALWAYS_FATAL("Unsupported wide color space.");
+ }
+ mHasWideColorGamutSupport = EglExtensions.glColorSpace && hasWideColorSpaceExtension &&
+ mEglConfigWideGamut != EGL_NO_CONFIG_KHR;
+}
+
+EGLConfig EglManager::load8BitsConfig(EGLDisplay display, EglManager::SwapBehavior swapBehavior) {
+ EGLint eglSwapBehavior =
+ (swapBehavior == SwapBehavior::Preserved) ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0;
+ EGLint attribs[] = {EGL_RENDERABLE_TYPE,
+ EGL_OPENGL_ES2_BIT,
+ EGL_RED_SIZE,
+ 8,
+ EGL_GREEN_SIZE,
+ 8,
+ EGL_BLUE_SIZE,
+ 8,
+ EGL_ALPHA_SIZE,
+ 8,
+ EGL_DEPTH_SIZE,
+ 0,
+ EGL_CONFIG_CAVEAT,
+ EGL_NONE,
+ EGL_STENCIL_SIZE,
+ STENCIL_BUFFER_SIZE,
+ EGL_SURFACE_TYPE,
+ EGL_WINDOW_BIT | eglSwapBehavior,
+ EGL_NONE};
+ EGLConfig config = EGL_NO_CONFIG_KHR;
+ EGLint numConfigs = 1;
+ if (!eglChooseConfig(display, attribs, &config, numConfigs, &numConfigs) ||
+ numConfigs != 1) {
+ return EGL_NO_CONFIG_KHR;
+ }
+ return config;
+}
+
+EGLConfig EglManager::loadFP16Config(EGLDisplay display, SwapBehavior swapBehavior) {
+ EGLint eglSwapBehavior =
+ (swapBehavior == SwapBehavior::Preserved) ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0;
+ // If we reached this point, we have a valid swap behavior
+ EGLint attribs[] = {EGL_RENDERABLE_TYPE,
+ EGL_OPENGL_ES2_BIT,
+ EGL_COLOR_COMPONENT_TYPE_EXT,
+ EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT,
+ EGL_RED_SIZE,
+ 16,
+ EGL_GREEN_SIZE,
+ 16,
+ EGL_BLUE_SIZE,
+ 16,
+ EGL_ALPHA_SIZE,
+ 16,
+ EGL_DEPTH_SIZE,
+ 0,
+ EGL_STENCIL_SIZE,
+ STENCIL_BUFFER_SIZE,
+ EGL_SURFACE_TYPE,
+ EGL_WINDOW_BIT | eglSwapBehavior,
+ EGL_NONE};
+ EGLConfig config = EGL_NO_CONFIG_KHR;
+ EGLint numConfigs = 1;
+ if (!eglChooseConfig(display, attribs, &config, numConfigs, &numConfigs) ||
+ numConfigs != 1) {
+ return EGL_NO_CONFIG_KHR;
+ }
+ return config;
}
void EglManager::initExtensions() {
@@ -146,12 +222,8 @@ void EglManager::initExtensions() {
EglExtensions.glColorSpace = extensions.has("EGL_KHR_gl_colorspace");
EglExtensions.noConfigContext = extensions.has("EGL_KHR_no_config_context");
EglExtensions.pixelFormatFloat = extensions.has("EGL_EXT_pixel_format_float");
-#ifdef ANDROID_ENABLE_LINEAR_BLENDING
- EglExtensions.scRGB = extensions.has("EGL_EXT_gl_colorspace_scrgb_linear");
-#else
EglExtensions.scRGB = extensions.has("EGL_EXT_gl_colorspace_scrgb");
-#endif
- EglExtensions.displayP3 = extensions.has("EGL_EXT_gl_colorspace_display_p3");
+ EglExtensions.displayP3 = extensions.has("EGL_EXT_gl_colorspace_display_p3_passthrough");
EglExtensions.contextPriority = extensions.has("EGL_IMG_context_priority");
EglExtensions.surfacelessContext = extensions.has("EGL_KHR_surfaceless_context");
}
@@ -162,77 +234,35 @@ bool EglManager::hasEglContext() {
void EglManager::loadConfigs() {
ALOGD("Swap behavior %d", static_cast<int>(mSwapBehavior));
- EGLint swapBehavior =
- (mSwapBehavior == SwapBehavior::Preserved) ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0;
// Note: The default pixel format is RGBA_8888, when other formats are
// available, we should check the target pixel format and configure the
// attributes list properly.
- EGLint attribs[] = {EGL_RENDERABLE_TYPE,
- EGL_OPENGL_ES2_BIT,
- EGL_RED_SIZE,
- 8,
- EGL_GREEN_SIZE,
- 8,
- EGL_BLUE_SIZE,
- 8,
- EGL_ALPHA_SIZE,
- 8,
- EGL_DEPTH_SIZE,
- 0,
- EGL_CONFIG_CAVEAT,
- EGL_NONE,
- EGL_STENCIL_SIZE,
- STENCIL_BUFFER_SIZE,
- EGL_SURFACE_TYPE,
- EGL_WINDOW_BIT | swapBehavior,
- EGL_NONE};
-
- EGLint numConfigs = 1;
- if (!eglChooseConfig(mEglDisplay, attribs, &mEglConfig, numConfigs, &numConfigs) ||
- numConfigs != 1) {
+ mEglConfig = load8BitsConfig(mEglDisplay, mSwapBehavior);
+ if (mEglConfig == EGL_NO_CONFIG_KHR) {
if (mSwapBehavior == SwapBehavior::Preserved) {
// Try again without dirty regions enabled
ALOGW("Failed to choose config with EGL_SWAP_BEHAVIOR_PRESERVED, retrying without...");
mSwapBehavior = SwapBehavior::Discard;
- loadConfigs();
- return; // the call to loadConfigs() we just made picks the wide gamut config
+ ALOGD("Swap behavior %d", static_cast<int>(mSwapBehavior));
+ mEglConfig = load8BitsConfig(mEglDisplay, mSwapBehavior);
} else {
// Failed to get a valid config
LOG_ALWAYS_FATAL("Failed to choose config, error = %s", eglErrorString());
}
}
+ SkColorType wideColorType = DeviceInfo::get()->getWideColorType();
- if (EglExtensions.pixelFormatFloat) {
- // If we reached this point, we have a valid swap behavior
- EGLint attribs16F[] = {EGL_RENDERABLE_TYPE,
- EGL_OPENGL_ES2_BIT,
- EGL_COLOR_COMPONENT_TYPE_EXT,
- EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT,
- EGL_RED_SIZE,
- 16,
- EGL_GREEN_SIZE,
- 16,
- EGL_BLUE_SIZE,
- 16,
- EGL_ALPHA_SIZE,
- 16,
- EGL_DEPTH_SIZE,
- 0,
- EGL_STENCIL_SIZE,
- STENCIL_BUFFER_SIZE,
- EGL_SURFACE_TYPE,
- EGL_WINDOW_BIT | swapBehavior,
- EGL_NONE};
-
- numConfigs = 1;
- if (!eglChooseConfig(mEglDisplay, attribs16F, &mEglConfigWideGamut, numConfigs,
- &numConfigs) ||
- numConfigs != 1) {
+ // When we reach this point, we have a valid swap behavior
+ if (wideColorType == SkColorType::kRGBA_F16_SkColorType && EglExtensions.pixelFormatFloat) {
+ mEglConfigWideGamut = loadFP16Config(mEglDisplay, mSwapBehavior);
+ if (mEglConfigWideGamut == EGL_NO_CONFIG_KHR) {
ALOGE("Device claims wide gamut support, cannot find matching config, error = %s",
eglErrorString());
EglExtensions.pixelFormatFloat = false;
}
+ } else if (wideColorType == SkColorType::kN32_SkColorType) {
+ mEglConfigWideGamut = load8BitsConfig(mEglDisplay, mSwapBehavior);
}
}
@@ -263,11 +293,12 @@ void EglManager::createPBufferSurface() {
}
}
-Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window, ColorMode colorMode) {
+Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window,
+ ColorMode colorMode,
+ SkColorSpace::Gamut colorGamut) {
LOG_ALWAYS_FATAL_IF(!hasEglContext(), "Not initialized");
- bool wideColorGamut = colorMode == ColorMode::WideColorGamut && EglExtensions.glColorSpace &&
- EglExtensions.scRGB && EglExtensions.pixelFormatFloat &&
+ bool wideColorGamut = colorMode == ColorMode::WideColorGamut && mHasWideColorGamutSupport &&
EglExtensions.noConfigContext;
// The color space we want to use depends on whether linear blending is turned
@@ -285,8 +316,8 @@ Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window,
// When wide gamut rendering is on we cannot rely on the GPU performing
// linear blending for us. We use two different color spaces to tag the
// surface appropriately for SurfaceFlinger:
- // - Gamma blending (default) requires the use of the scRGB-nl color space
- // - Linear blending requires the use of the scRGB color space
+ // - Gamma blending (default) requires the use of the non-linear color space
+ // - Linear blending requires the use of the linear color space
// Not all Android targets support the EGL_GL_COLORSPACE_KHR extension
// We insert to placeholders to set EGL_GL_COLORSPACE_KHR and its value.
@@ -296,19 +327,20 @@ Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window,
if (EglExtensions.glColorSpace) {
attribs[0] = EGL_GL_COLORSPACE_KHR;
-#ifdef ANDROID_ENABLE_LINEAR_BLENDING
- if (wideColorGamut) {
- attribs[1] = EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT;
- } else {
- attribs[1] = EGL_GL_COLORSPACE_SRGB_KHR;
- }
-#else
if (wideColorGamut) {
- attribs[1] = EGL_GL_COLORSPACE_SCRGB_EXT;
+ switch (colorGamut) {
+ case SkColorSpace::Gamut::kDCIP3_D65_Gamut:
+ attribs[1] = EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT;
+ break;
+ case SkColorSpace::Gamut::kSRGB_Gamut:
+ attribs[1] = EGL_GL_COLORSPACE_SCRGB_EXT;
+ break;
+ default:
+ LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space.");
+ }
} else {
attribs[1] = EGL_GL_COLORSPACE_LINEAR_KHR;
}
-#endif
}
EGLSurface surface = eglCreateWindowSurface(
diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h
index 2a44f7e10b80..4dd90961b4f7 100644
--- a/libs/hwui/renderthread/EglManager.h
+++ b/libs/hwui/renderthread/EglManager.h
@@ -48,7 +48,8 @@ public:
bool hasEglContext();
- Result<EGLSurface, EGLint> createSurface(EGLNativeWindowType window, ColorMode colorMode);
+ Result<EGLSurface, EGLint> createSurface(EGLNativeWindowType window, ColorMode colorMode,
+ SkColorSpace::Gamut colorGamut);
void destroySurface(EGLSurface surface);
void destroy();
@@ -80,6 +81,14 @@ public:
status_t createReleaseFence(bool useFenceSync, EGLSyncKHR* eglFence, sp<Fence>& nativeFence);
private:
+ enum class SwapBehavior {
+ Discard,
+ Preserved,
+ BufferAge,
+ };
+
+ static EGLConfig load8BitsConfig(EGLDisplay display, SwapBehavior swapBehavior);
+ static EGLConfig loadFP16Config(EGLDisplay display, SwapBehavior swapBehavior);
void initExtensions();
void createPBufferSurface();
@@ -93,12 +102,7 @@ private:
EGLContext mEglContext;
EGLSurface mPBufferSurface;
EGLSurface mCurrentSurface;
-
- enum class SwapBehavior {
- Discard,
- Preserved,
- BufferAge,
- };
+ bool mHasWideColorGamutSupport;
SwapBehavior mSwapBehavior = SwapBehavior::Discard;
};
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 4be8bd9a863e..aa7a141f6da3 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -473,8 +473,10 @@ SkSurface* VulkanManager::getBackbufferSurface(VulkanSurface** surfaceOut) {
if (windowWidth != surface->mWindowWidth || windowHeight != surface->mWindowHeight) {
ColorMode colorMode = surface->mColorMode;
sk_sp<SkColorSpace> colorSpace = surface->mColorSpace;
+ SkColorSpace::Gamut colorGamut = surface->mColorGamut;
+ SkColorType colorType = surface->mColorType;
destroySurface(surface);
- *surfaceOut = createSurface(window, colorMode, colorSpace);
+ *surfaceOut = createSurface(window, colorMode, colorSpace, colorGamut, colorType);
surface = *surfaceOut;
}
@@ -647,8 +649,7 @@ void VulkanManager::createBuffers(VulkanSurface* surface, VkFormat format, VkExt
VulkanSurface::ImageInfo& imageInfo = surface->mImageInfos[i];
imageInfo.mSurface = SkSurface::MakeFromBackendRenderTarget(
mRenderThread.getGrContext(), backendRT, kTopLeft_GrSurfaceOrigin,
- surface->mColorMode == ColorMode::WideColorGamut ? kRGBA_F16_SkColorType
- : kRGBA_8888_SkColorType, surface->mColorSpace, &props);
+ surface->mColorType, surface->mColorSpace, &props);
}
SkASSERT(mCommandPool != VK_NULL_HANDLE);
@@ -767,10 +768,20 @@ bool VulkanManager::createSwapchain(VulkanSurface* surface) {
VkFormat surfaceFormat = VK_FORMAT_R8G8B8A8_UNORM;
VkColorSpaceKHR colorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR;
- if (surface->mColorMode == ColorMode::WideColorGamut) {
+ if (surface->mColorType == SkColorType::kRGBA_F16_SkColorType) {
surfaceFormat = VK_FORMAT_R16G16B16A16_SFLOAT;
- colorSpace = VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT;
}
+
+ if (surface->mColorMode == ColorMode::WideColorGamut) {
+ if (surface->mColorGamut == SkColorSpace::Gamut::kSRGB_Gamut) {
+ colorSpace = VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT;
+ } else if (surface->mColorGamut == SkColorSpace::Gamut::kDCIP3_D65_Gamut) {
+ colorSpace = VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT;
+ } else {
+ LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space.");
+ }
+ }
+
bool foundSurfaceFormat = false;
for (uint32_t i = 0; i < surfaceFormatCount; ++i) {
if (surfaceFormat == surfaceFormats[i].format
@@ -840,14 +851,17 @@ bool VulkanManager::createSwapchain(VulkanSurface* surface) {
}
VulkanSurface* VulkanManager::createSurface(ANativeWindow* window, ColorMode colorMode,
- sk_sp<SkColorSpace> surfaceColorSpace) {
+ sk_sp<SkColorSpace> surfaceColorSpace,
+ SkColorSpace::Gamut surfaceColorGamut,
+ SkColorType surfaceColorType) {
initialize();
if (!window) {
return nullptr;
}
- VulkanSurface* surface = new VulkanSurface(colorMode, window, surfaceColorSpace);
+ VulkanSurface* surface = new VulkanSurface(colorMode, window, surfaceColorSpace,
+ surfaceColorGamut, surfaceColorType);
VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo;
memset(&surfaceCreateInfo, 0, sizeof(VkAndroidSurfaceCreateInfoKHR));
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index d67d2c81e95c..69ca23acb1fd 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -38,8 +38,10 @@ class RenderThread;
class VulkanSurface {
public:
- VulkanSurface(ColorMode colorMode, ANativeWindow* window, sk_sp<SkColorSpace> colorSpace)
- : mColorMode(colorMode), mNativeWindow(window), mColorSpace(colorSpace) {}
+ VulkanSurface(ColorMode colorMode, ANativeWindow* window, sk_sp<SkColorSpace> colorSpace,
+ SkColorSpace::Gamut colorGamut, SkColorType colorType)
+ : mColorMode(colorMode), mNativeWindow(window), mColorSpace(colorSpace),
+ mColorGamut(colorGamut), mColorType(colorType) {}
sk_sp<SkSurface> getBackBufferSurface() { return mBackbuffer; }
@@ -80,6 +82,8 @@ private:
int mWindowWidth = 0;
int mWindowHeight = 0;
sk_sp<SkColorSpace> mColorSpace;
+ SkColorSpace::Gamut mColorGamut;
+ SkColorType mColorType;
};
// This class contains the shared global Vulkan objects, such as VkInstance, VkDevice and VkQueue,
@@ -98,7 +102,9 @@ public:
// Given a window this creates a new VkSurfaceKHR and VkSwapchain and stores them inside a new
// VulkanSurface object which is returned.
VulkanSurface* createSurface(ANativeWindow* window, ColorMode colorMode,
- sk_sp<SkColorSpace> surfaceColorSpace);
+ sk_sp<SkColorSpace> surfaceColorSpace,
+ SkColorSpace::Gamut surfaceColorGamut,
+ SkColorType surfaceColorType);
// Destroy the VulkanSurface and all associated vulkan objects.
void destroySurface(VulkanSurface* surface);
diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp
index a6073ebb5c74..75fb0ef0acc1 100644
--- a/libs/hwui/tests/unit/RenderNodeTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeTests.cpp
@@ -306,6 +306,7 @@ RENDERTHREAD_TEST(RenderNode, prepareTree_HwLayer_AVD_enqueueDamage) {
ContextFactory contextFactory;
std::unique_ptr<CanvasContext> canvasContext(
CanvasContext::create(renderThread, false, rootNode.get(), &contextFactory));
+ canvasContext->setSurface(nullptr);
TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
DamageAccumulator damageAccumulator;
LayerUpdateQueue layerUpdateQueue;
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index 32c752064a69..05d49e592747 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -88,6 +88,10 @@ interface ILocationManager
boolean providerMeetsCriteria(String provider, in Criteria criteria);
ProviderProperties getProviderProperties(String provider);
String getNetworkProviderPackage();
+ void setLocationControllerExtraPackage(String packageName);
+ String getLocationControllerExtraPackage();
+ void setLocationControllerExtraPackageEnabled(boolean enabled);
+ boolean isLocationControllerExtraPackageEnabled();
boolean isProviderEnabledForUser(String provider, int userId);
boolean setProviderEnabledForUser(String provider, boolean enabled, int userId);
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 334170e5ce03..1cd3d86a0c27 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -2396,4 +2396,65 @@ public class LocationManager {
return null;
}
}
+
+ /**
+ * Set the extra location controller package for location services on the device.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.LOCATION_HARDWARE)
+ public void setLocationControllerExtraPackage(String packageName) {
+ try {
+ mService.setLocationControllerExtraPackage(packageName);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the extra location controller package on the device.
+ *
+ * @hide
+ */
+ @SystemApi
+ public @Nullable String getLocationControllerExtraPackage() {
+ try {
+ return mService.getLocationControllerExtraPackage();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ return null;
+ }
+ }
+
+ /**
+ * Set whether the extra location controller package is currently enabled on the device.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.LOCATION_HARDWARE)
+ public void setLocationControllerExtraPackageEnabled(boolean enabled) {
+ try {
+ mService.setLocationControllerExtraPackageEnabled(enabled);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns whether extra location controller package is currently enabled on the device.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean isLocationControllerExtraPackageEnabled() {
+ try {
+ return mService.isLocationControllerExtraPackageEnabled();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ return false;
+ }
+ }
+
}
diff --git a/media/java/android/media/DataSourceCallback.java b/media/java/android/media/DataSourceCallback.java
index 0d4d53106c78..1afcd2075ba4 100644
--- a/media/java/android/media/DataSourceCallback.java
+++ b/media/java/android/media/DataSourceCallback.java
@@ -47,7 +47,7 @@ public abstract class DataSourceCallback implements Closeable {
* @param offset the offset within buffer to read the data into.
* @param size the number of bytes to read.
* @throws IOException on fatal errors.
- * @return the number of bytes read, or -1 if there was an error.
+ * @return the number of bytes read, or -1 if end of stream is reached.
*/
public abstract int readAt(long position, byte[] buffer, int offset, int size)
throws IOException;
diff --git a/media/java/android/media/MediaDataSource.java b/media/java/android/media/MediaDataSource.java
index 4ba2120f5b09..4bdc1adcc3f0 100644
--- a/media/java/android/media/MediaDataSource.java
+++ b/media/java/android/media/MediaDataSource.java
@@ -46,7 +46,7 @@ public abstract class MediaDataSource implements Closeable {
* @param offset the offset within buffer to read the data into.
* @param size the number of bytes to read.
* @throws IOException on fatal errors.
- * @return the number of bytes read, or -1 if there was an error.
+ * @return the number of bytes read, or -1 if end of stream is reached.
*/
public abstract int readAt(long position, byte[] buffer, int offset, int size)
throws IOException;
diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl
index bd0019f0f1d8..bfc05fa4e3a4 100644
--- a/media/java/android/media/session/ISession.aidl
+++ b/media/java/android/media/session/ISession.aidl
@@ -39,7 +39,7 @@ interface ISession {
void destroy();
// These commands are for the TransportPerformer
- void setMetadata(in MediaMetadata metadata);
+ void setMetadata(in MediaMetadata metadata, long duration, String metadataDescription);
void setPlaybackState(in PlaybackState state);
void setQueue(in ParceledListSlice queue);
void setQueueTitle(CharSequence title);
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index d43cd309d157..8962bb7fb4c4 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -30,6 +30,7 @@ import android.media.MediaDescription;
import android.media.MediaMetadata;
import android.media.Rating;
import android.media.VolumeProvider;
+import android.media.session.MediaSessionManager.RemoteUserInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -40,7 +41,6 @@ import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.UserHandle;
-import android.media.session.MediaSessionManager.RemoteUserInfo;
import android.service.media.MediaBrowserService;
import android.text.TextUtils;
import android.util.Log;
@@ -434,11 +434,21 @@ public final class MediaSession {
* @see android.media.MediaMetadata.Builder#putBitmap
*/
public void setMetadata(@Nullable MediaMetadata metadata) {
+ long duration = -1;
+ int fields = 0;
+ MediaDescription description = null;
if (metadata != null) {
metadata = (new MediaMetadata.Builder(metadata, mMaxBitmapSize)).build();
+ if (metadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
+ duration = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
+ }
+ fields = metadata.size();
+ description = metadata.getDescription();
}
+ String metadataDescription = "size=" + fields + ", description=" + description;
+
try {
- mBinder.setMetadata(metadata);
+ mBinder.setMetadata(metadata, duration, metadataDescription);
} catch (RemoteException e) {
Log.wtf(TAG, "Dead object in setPlaybackState.", e);
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java b/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java
index cb92c4259504..5e63b90203a5 100644
--- a/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java
+++ b/packages/CarSystemUI/src/com/android/systemui/notifications/NotificationsUI.java
@@ -16,6 +16,7 @@
package com.android.systemui.notifications;
+import android.app.ActivityManager;
import android.car.Car;
import android.car.CarNotConnectedException;
import android.car.drivingstate.CarUxRestrictionsManager;
@@ -24,6 +25,7 @@ import android.content.Context;
import android.content.ServiceConnection;
import android.graphics.PixelFormat;
import android.os.IBinder;
+import android.os.ServiceManager;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -31,8 +33,10 @@ import android.view.WindowManager;
import com.android.car.notification.CarNotificationListener;
import com.android.car.notification.CarUxRestrictionManagerWrapper;
+import com.android.car.notification.NotificationClickHandlerFactory;
import com.android.car.notification.NotificationViewController;
import com.android.car.notification.PreprocessingManager;
+import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
@@ -44,6 +48,7 @@ public class NotificationsUI extends SystemUI {
private static final String TAG = "NotificationsUI";
private CarNotificationListener mCarNotificationListener;
private CarUxRestrictionsManager mCarUxRestrictionsManager;
+ private NotificationClickHandlerFactory mClickHandlerFactory;
private Car mCar;
private ViewGroup mCarNotificationWindow;
private NotificationViewController mNotificationViewController;
@@ -60,7 +65,17 @@ public class NotificationsUI extends SystemUI {
WindowManager windowManager =
(WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
mCarNotificationListener = new CarNotificationListener();
- mCarNotificationListener.registerAsSystemService(mContext, mCarUxRestrictionManagerWrapper);
+ mClickHandlerFactory = new NotificationClickHandlerFactory(
+ IStatusBarService.Stub.asInterface(
+ ServiceManager.getService(Context.STATUS_BAR_SERVICE)),
+ launchResult -> {
+ if (launchResult == ActivityManager.START_TASK_TO_FRONT
+ || launchResult == ActivityManager.START_SUCCESS) {
+ closeCarNotifications();
+ }
+ });
+ mCarNotificationListener.registerAsSystemService(mContext, mCarUxRestrictionManagerWrapper,
+ mClickHandlerFactory);
mCar = Car.createCar(mContext, mCarConnectionListener);
mCar.connect();
diff --git a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
index 1737b644f5d3..85dab57b5f21 100644
--- a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
+++ b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
@@ -107,36 +107,44 @@ public class CarVolumeDialogImpl implements VolumeDialog {
private CarAudioManager mCarAudioManager;
private final CarAudioManager.CarVolumeCallback mVolumeChangeCallback =
new CarAudioManager.CarVolumeCallback() {
- @Override
- public void onGroupVolumeChanged(int zoneId, int groupId, int flags) {
- // TODO: Include zoneId into consideration.
- // For instance
- // - single display + single-zone, ignore zoneId
- // - multi-display + single-zone, zoneId is fixed, may show volume bar on all displays
- // - single-display + multi-zone, may show volume bar on primary display only
- // - multi-display + multi-zone, may show volume bar on display specified by zoneId
- VolumeItem volumeItem = mAvailableVolumeItems.get(groupId);
- int value = getSeekbarValue(mCarAudioManager, groupId);
- // Do not update the progress if it is the same as before. When car audio manager sets
- // its group volume caused by the seekbar progress changed, it also triggers this
- // callback. Updating the seekbar at the same time could block the continuous seeking.
- if (value != volumeItem.progress) {
- volumeItem.listItem.setProgress(value);
- volumeItem.progress = value;
- }
- if ((flags & AudioManager.FLAG_SHOW_UI) != 0) {
- mHandler.obtainMessage(H.SHOW, Events.SHOW_REASON_VOLUME_CHANGED).sendToTarget();
- }
- }
+ @Override
+ public void onGroupVolumeChanged(int zoneId, int groupId, int flags) {
+ // TODO: Include zoneId into consideration.
+ // For instance
+ // - single display + single-zone, ignore zoneId
+ // - multi-display + single-zone, zoneId is fixed, may show volume bar on all
+ // displays
+ // - single-display + multi-zone, may show volume bar on primary display only
+ // - multi-display + multi-zone, may show volume bar on display specified by
+ // zoneId
+ VolumeItem volumeItem = mAvailableVolumeItems.get(groupId);
+ int value = getSeekbarValue(mCarAudioManager, groupId);
+ // Do not update the progress if it is the same as before. When car audio
+ // manager sets
+ // its group volume caused by the seekbar progress changed, it also triggers
+ // this
+ // callback. Updating the seekbar at the same time could block the continuous
+ // seeking.
+ if (value != volumeItem.progress) {
+ volumeItem.listItem.setProgress(value);
+ volumeItem.progress = value;
+ }
+ if ((flags & AudioManager.FLAG_SHOW_UI) != 0) {
+ mHandler.obtainMessage(H.SHOW,
+ Events.SHOW_REASON_VOLUME_CHANGED).sendToTarget();
+ }
+ }
- @Override
- public void onMasterMuteChanged(int zoneId, int flags) {
- // ignored
- }
- };
+ @Override
+ public void onMasterMuteChanged(int zoneId, int flags) {
+ // ignored
+ }
+ };
private boolean mHovering;
private boolean mShowing;
private boolean mExpanded;
+ private View mExpandIcon;
+ private VolumeItem mDefaultVolumeItem;
private final ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
@@ -151,10 +159,8 @@ public class CarVolumeDialogImpl implements VolumeDialog {
mAvailableVolumeItems.add(volumeItem);
// The first one is the default item.
if (groupId == 0) {
- volumeItem.defaultItem = true;
- addSeekbarListItem(volumeItem, groupId,
- R.drawable.car_ic_keyboard_arrow_down,
- new ExpandIconListener());
+ mDefaultVolumeItem = volumeItem;
+ setupDefaultListItem();
}
}
@@ -178,6 +184,13 @@ public class CarVolumeDialogImpl implements VolumeDialog {
}
};
+ private void setupDefaultListItem() {
+ mDefaultVolumeItem.defaultItem = true;
+ addSeekbarListItem(mDefaultVolumeItem, /* volumeGroupId = */0,
+ R.drawable.car_ic_keyboard_arrow_down, new ExpandIconListener()
+ );
+ }
+
public CarVolumeDialogImpl(Context context) {
mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
@@ -294,7 +307,9 @@ public class CarVolumeDialogImpl implements VolumeDialog {
return;
}
mShowing = true;
-
+ if (mVolumeLineItems.isEmpty()) {
+ setupDefaultListItem();
+ }
mDialog.show();
Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
}
@@ -340,6 +355,13 @@ public class CarVolumeDialogImpl implements VolumeDialog {
}
mDialog.dismiss();
mShowing = false;
+ mShowing = false;
+ // if mExpandIcon is null that means user never clicked on the expanded arrow
+ // which implies that the dialog is still not expanded. In that case we do
+ // not want to reset the state
+ if (mExpandIcon != null && mExpanded) {
+ toggleDialogExpansion(/* isClicked = */ false);
+ }
}, DISMISS_DELAY_IN_MILLIS))
.start();
@@ -517,50 +539,60 @@ public class CarVolumeDialogImpl implements VolumeDialog {
}
private final class ExpandIconListener implements View.OnClickListener {
-
@Override
public void onClick(final View v) {
- mExpanded = !mExpanded;
- Animator inAnimator;
- if (mExpanded) {
- for (int groupId = 0; groupId < mAvailableVolumeItems.size(); ++groupId) {
- // Adding the items which are not coming from the default item.
- VolumeItem volumeItem = mAvailableVolumeItems.get(groupId);
- if (volumeItem.defaultItem) {
- // Set progress here due to the progress of seekbar may not be updated.
- volumeItem.listItem.setProgress(volumeItem.progress);
- } else {
- addSeekbarListItem(volumeItem, groupId, 0, null);
- }
+ mExpandIcon = v;
+ toggleDialogExpansion(true);
+ }
+ }
+
+ private void toggleDialogExpansion(boolean isClicked) {
+ mExpanded = !mExpanded;
+ Animator inAnimator;
+ if (mExpanded) {
+ for (int groupId = 0; groupId < mAvailableVolumeItems.size(); ++groupId) {
+ // Adding the items which are not coming from the default item.
+ VolumeItem volumeItem = mAvailableVolumeItems.get(groupId);
+ if (volumeItem.defaultItem) {
+ // Set progress here due to the progress of seekbar may not be updated.
+ volumeItem.listItem.setProgress(volumeItem.progress);
+ } else {
+ addSeekbarListItem(volumeItem, groupId, 0, null);
}
- inAnimator = AnimatorInflater.loadAnimator(
- mContext, R.anim.car_arrow_fade_in_rotate_up);
- } else {
- // Only keeping the default stream if it is not expended.
- Iterator itr = mVolumeLineItems.iterator();
- while (itr.hasNext()) {
- SeekbarListItem seekbarListItem = (SeekbarListItem) itr.next();
- VolumeItem volumeItem = findVolumeItem(seekbarListItem);
- if (!volumeItem.defaultItem) {
- itr.remove();
- } else {
- // Set progress here due to the progress of seekbar may not be updated.
- seekbarListItem.setProgress(volumeItem.progress);
- }
+ }
+ inAnimator = AnimatorInflater.loadAnimator(
+ mContext, R.anim.car_arrow_fade_in_rotate_up);
+
+ } else {
+ // Only keeping the default stream if it is not expended.
+ Iterator itr = mVolumeLineItems.iterator();
+ while (itr.hasNext()) {
+ SeekbarListItem seekbarListItem = (SeekbarListItem) itr.next();
+ VolumeItem volumeItem = findVolumeItem(seekbarListItem);
+ if (!volumeItem.defaultItem) {
+ itr.remove();
+ } else {
+ // Set progress here due to the progress of seekbar may not be updated.
+ seekbarListItem.setProgress(volumeItem.progress);
}
- inAnimator = AnimatorInflater.loadAnimator(
- mContext, R.anim.car_arrow_fade_in_rotate_down);
}
+ inAnimator = AnimatorInflater.loadAnimator(
+ mContext, R.anim.car_arrow_fade_in_rotate_down);
+ }
- Animator outAnimator = AnimatorInflater.loadAnimator(
- mContext, R.anim.car_arrow_fade_out);
- inAnimator.setStartDelay(ARROW_FADE_IN_START_DELAY_IN_MILLIS);
- AnimatorSet animators = new AnimatorSet();
- animators.playTogether(outAnimator, inAnimator);
- animators.setTarget(v);
- animators.start();
- mPagedListAdapter.notifyDataSetChanged();
+ Animator outAnimator = AnimatorInflater.loadAnimator(
+ mContext, R.anim.car_arrow_fade_out);
+ inAnimator.setStartDelay(ARROW_FADE_IN_START_DELAY_IN_MILLIS);
+ AnimatorSet animators = new AnimatorSet();
+ animators.playTogether(outAnimator, inAnimator);
+ if (!isClicked) {
+ // Do not animate when the state is called to reset the dialogs view and not clicked
+ // by user.
+ animators.setDuration(0);
}
+ animators.setTarget(mExpandIcon);
+ animators.start();
+ mPagedListAdapter.notifyDataSetChanged();
}
private final class VolumeSeekBarChangeListener implements OnSeekBarChangeListener {
@@ -601,4 +633,4 @@ public class CarVolumeDialogImpl implements VolumeDialog {
public void onStopTrackingTouch(SeekBar seekBar) {
}
}
-}
+} \ No newline at end of file
diff --git a/packages/SettingsLib/ActionButtonsPreference/Android.bp b/packages/SettingsLib/ActionButtonsPreference/Android.bp
index e518e0b97c4b..cd3fb0cc2143 100644
--- a/packages/SettingsLib/ActionButtonsPreference/Android.bp
+++ b/packages/SettingsLib/ActionButtonsPreference/Android.bp
@@ -1,5 +1,5 @@
android_library {
- name: "ActionButtonsPreference",
+ name: "SettingsLibActionButtonsPreference",
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 0126e7e59915..042808a07f8f 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -16,8 +16,8 @@ android_library {
"SettingsLibAppPreference",
"SettingsLibSearchWidget",
"SettingsLibSettingsSpinner",
- "SettingsLayoutPreference",
- "ActionButtonsPreference",
+ "SettingsLibLayoutPreference",
+ "SettingsLibActionButtonsPreference",
"SettingsLibEntityHeaderWidgets",
],
diff --git a/packages/SettingsLib/SettingsLayoutPreference/Android.bp b/packages/SettingsLib/LayoutPreference/Android.bp
index 489d3606ae15..a1f9a768c76c 100644
--- a/packages/SettingsLib/SettingsLayoutPreference/Android.bp
+++ b/packages/SettingsLib/LayoutPreference/Android.bp
@@ -1,5 +1,5 @@
android_library {
- name: "SettingsLayoutPreference",
+ name: "SettingsLibLayoutPreference",
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/SettingsLayoutPreference/AndroidManifest.xml b/packages/SettingsLib/LayoutPreference/AndroidManifest.xml
index 4b9f1ab8d6cc..4b9f1ab8d6cc 100644
--- a/packages/SettingsLib/SettingsLayoutPreference/AndroidManifest.xml
+++ b/packages/SettingsLib/LayoutPreference/AndroidManifest.xml
diff --git a/packages/SettingsLib/SettingsLayoutPreference/res/layout/layout_preference_frame.xml b/packages/SettingsLib/LayoutPreference/res/layout/layout_preference_frame.xml
index ee4ce499396f..ee4ce499396f 100644
--- a/packages/SettingsLib/SettingsLayoutPreference/res/layout/layout_preference_frame.xml
+++ b/packages/SettingsLib/LayoutPreference/res/layout/layout_preference_frame.xml
diff --git a/packages/SettingsLib/SettingsLayoutPreference/res/layout/settings_entity_header.xml b/packages/SettingsLib/LayoutPreference/res/layout/settings_entity_header.xml
index e27ae7d6d439..e27ae7d6d439 100644
--- a/packages/SettingsLib/SettingsLayoutPreference/res/layout/settings_entity_header.xml
+++ b/packages/SettingsLib/LayoutPreference/res/layout/settings_entity_header.xml
diff --git a/packages/SettingsLib/SettingsLayoutPreference/res/values/styles.xml b/packages/SettingsLib/LayoutPreference/res/values/styles.xml
index 805744baef10..805744baef10 100644
--- a/packages/SettingsLib/SettingsLayoutPreference/res/values/styles.xml
+++ b/packages/SettingsLib/LayoutPreference/res/values/styles.xml
diff --git a/packages/SettingsLib/SettingsLayoutPreference/src/com/android/settingslib/widget/LayoutPreference.java b/packages/SettingsLib/LayoutPreference/src/com/android/settingslib/widget/LayoutPreference.java
index 2a635b0996e6..2a635b0996e6 100644
--- a/packages/SettingsLib/SettingsLayoutPreference/src/com/android/settingslib/widget/LayoutPreference.java
+++ b/packages/SettingsLib/LayoutPreference/src/com/android/settingslib/widget/LayoutPreference.java
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
index 5c126b1d839b..120acd3bcc52 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
@@ -263,6 +263,10 @@ public class InputMethodPreference extends RestrictedSwitchPreference implements
// The user canceled to enable a 3rd party IME.
setCheckedInternal(false);
});
+ builder.setOnCancelListener((dialog) -> {
+ // The user canceled to enable a 3rd party IME.
+ setCheckedInternal(false);
+ });
mDialog = builder.create();
mDialog.show();
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index 352091804de2..f2b2719f2ee6 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -20,7 +20,6 @@ import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.app.ActivityManager;
import android.content.IContentProvider;
-import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Process;
@@ -28,6 +27,7 @@ import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.ShellCommand;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import java.io.FileDescriptor;
@@ -46,13 +46,6 @@ import java.util.Map;
*/
@SystemApi
public final class DeviceConfigService extends Binder {
- /**
- * TODO(b/113100523): Move this to DeviceConfig.java when it is added, and expose it as a System
- * API.
- */
- private static final Uri CONFIG_CONTENT_URI =
- Uri.parse("content://" + Settings.AUTHORITY + "/config");
-
final SettingsProvider mProvider;
public DeviceConfigService(SettingsProvider provider) {
@@ -191,10 +184,10 @@ public final class DeviceConfigService extends Binder {
final PrintWriter pout = getOutPrintWriter();
switch (verb) {
case GET:
- pout.println(get(iprovider, namespace, key));
+ pout.println(DeviceConfig.getProperty(namespace, key));
break;
case PUT:
- put(iprovider, namespace, key, value, makeDefault);
+ DeviceConfig.setProperty(namespace, key, value, makeDefault);
break;
case DELETE:
pout.println(delete(iprovider, namespace, key)
@@ -207,7 +200,7 @@ public final class DeviceConfigService extends Binder {
}
break;
case RESET:
- reset(iprovider, resetMode, namespace);
+ DeviceConfig.resetToDefaults(resetMode, namespace);
break;
default:
perr.println("Unspecified command");
@@ -241,43 +234,6 @@ public final class DeviceConfigService extends Binder {
+ "flags are reset");
}
- private String get(IContentProvider provider, String namespace, String key) {
- String compositeKey = namespace + "/" + key;
- String result = null;
- try {
- Bundle args = new Bundle();
- args.putInt(Settings.CALL_METHOD_USER_KEY,
- ActivityManager.getService().getCurrentUser().id);
- Bundle b = provider.call(resolveCallingPackage(), Settings.CALL_METHOD_GET_CONFIG,
- compositeKey, args);
- if (b != null) {
- result = b.getPairValue();
- }
- } catch (RemoteException e) {
- throw new RuntimeException("Failed in IPC", e);
- }
- return result;
- }
-
- private void put(IContentProvider provider, String namespace, String key, String value,
- boolean makeDefault) {
- String compositeKey = namespace + "/" + key;
-
- try {
- Bundle args = new Bundle();
- args.putString(Settings.NameValueTable.VALUE, value);
- args.putInt(Settings.CALL_METHOD_USER_KEY,
- ActivityManager.getService().getCurrentUser().id);
- if (makeDefault) {
- args.putBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY, true);
- }
- provider.call(resolveCallingPackage(), Settings.CALL_METHOD_PUT_CONFIG,
- compositeKey, args);
- } catch (RemoteException e) {
- throw new RuntimeException("Failed in IPC", e);
- }
- }
-
private boolean delete(IContentProvider provider, String namespace, String key) {
String compositeKey = namespace + "/" + key;
boolean success;
@@ -322,20 +278,6 @@ public final class DeviceConfigService extends Binder {
return lines;
}
- private void reset(IContentProvider provider, int resetMode, @Nullable String namespace) {
- try {
- Bundle args = new Bundle();
- args.putInt(Settings.CALL_METHOD_USER_KEY,
- ActivityManager.getService().getCurrentUser().id);
- args.putInt(Settings.CALL_METHOD_RESET_MODE_KEY, resetMode);
- args.putString(Settings.CALL_METHOD_PREFIX_KEY, namespace);
- provider.call(
- resolveCallingPackage(), Settings.CALL_METHOD_RESET_CONFIG, null, args);
- } catch (RemoteException e) {
- throw new RuntimeException("Failed in IPC", e);
- }
- }
-
private static String resolveCallingPackage() {
switch (Binder.getCallingUid()) {
case Process.ROOT_UID: {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index b071355986f5..ce529a085e77 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -63,6 +63,7 @@ import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManagerInternal;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.provider.Settings.Secure;
@@ -195,13 +196,6 @@ public class SettingsProvider extends ContentProvider {
private static final Set<String> OVERLAY_ALLOWED_SYSTEM_INSTANT_APP_SETTINGS = new ArraySet<>();
private static final Set<String> OVERLAY_ALLOWED_SECURE_INSTANT_APP_SETTINGS = new ArraySet<>();
- /**
- * TODO(b/113100523): Move this to DeviceConfig.java when it is added, and expose it as a System
- * API.
- */
- private static final Uri CONFIG_CONTENT_URI =
- Uri.parse("content://" + Settings.AUTHORITY + "/config");
-
static {
for (String name : Resources.getSystem().getStringArray(
com.android.internal.R.array.config_allowedGlobalInstantAppSettings)) {
@@ -3148,8 +3142,8 @@ public class SettingsProvider extends ContentProvider {
private Uri getNotificationUriFor(int key, String name) {
if (isConfigSettingsKey(key)) {
- return (name != null) ? Uri.withAppendedPath(CONFIG_CONTENT_URI, name)
- : CONFIG_CONTENT_URI;
+ return (name != null) ? Uri.withAppendedPath(DeviceConfig.CONTENT_URI, name)
+ : DeviceConfig.CONTENT_URI;
} else if (isGlobalSettingsKey(key)) {
return (name != null) ? Uri.withAppendedPath(Settings.Global.CONTENT_URI, name)
: Settings.Global.CONTENT_URI;
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
index 9d0462e14b63..5587cba59150 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
@@ -21,8 +21,8 @@ import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import android.content.ContentResolver;
-import android.net.Uri;
import android.os.Bundle;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
@@ -43,12 +43,6 @@ import java.io.InputStream;
*/
@RunWith(AndroidJUnit4.class)
public class DeviceConfigServiceTest {
- /**
- * TODO(b/113100523): Move this to DeviceConfig.java when it is added, and expose it as a System
- * API.
- */
- private static final Uri CONFIG_CONTENT_URI =
- Uri.parse("content://" + Settings.AUTHORITY + "/config");
private static final String sNamespace = "namespace1";
private static final String sKey = "key1";
private static final String sValue = "value1";
@@ -152,7 +146,7 @@ public class DeviceConfigServiceTest {
// make sValue the default value
executeShellCommand(
"device_config put " + sNamespace + " " + sKey + " " + sValue + " default");
- // make newValue the current value
+ // make newValue the current value (as set by a trusted package)
executeShellCommand(
"device_config put " + sNamespace + " " + sKey + " " + newValue);
String result = getFromContentProvider(mContentResolver, sNamespace, sKey);
@@ -161,14 +155,14 @@ public class DeviceConfigServiceTest {
// reset values that were set by untrusted packages
executeShellCommand("device_config reset untrusted_defaults " + sNamespace);
result = getFromContentProvider(mContentResolver, sNamespace, sKey);
- // the default value has been restored
- assertEquals(sValue, result);
+ // the current value was set by a trusted package, so it's not reset
+ assertEquals(newValue, result);
- // clear values that were set by untrusted packages
+ // reset values that were set by untrusted or trusted packages
executeShellCommand("device_config reset trusted_defaults " + sNamespace);
result = getFromContentProvider(mContentResolver, sNamespace, sKey);
- // even the default value is gone now
- assertNull(result);
+ // the default value has been restored
+ assertEquals(sValue, result);
}
private static void executeShellCommand(String command) throws IOException {
@@ -190,14 +184,15 @@ public class DeviceConfigServiceTest {
if (makeDefault) {
args.putBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY, true);
}
- resolver.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, compositeName, args);
+ resolver.call(
+ DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, compositeName, args);
}
private static String getFromContentProvider(ContentResolver resolver, String namespace,
String key) {
String compositeName = namespace + "/" + key;
Bundle result = resolver.call(
- CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, compositeName, null);
+ DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, compositeName, null);
assertNotNull(result);
return result.getString(Settings.NameValueTable.VALUE);
}
@@ -206,7 +201,7 @@ public class DeviceConfigServiceTest {
String key) {
String compositeName = namespace + "/" + key;
Bundle result = resolver.call(
- CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, compositeName, null);
+ DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, compositeName, null);
assertNotNull(result);
return compositeName.equals(result.getString(Settings.NameValueTable.VALUE));
}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 75492f340109..c2e107a06692 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -44,7 +44,6 @@ android_library {
"SystemUIPluginLib",
"SystemUISharedLib",
"SettingsLib",
- "androidx.car_car",
"androidx.legacy_legacy-support-v4",
"androidx.recyclerview_recyclerview",
"androidx.preference_preference",
@@ -67,8 +66,6 @@ android_library {
libs: [
"telephony-common",
- "android.car",
- "android.car.userlib",
],
aaptflags: [
@@ -98,7 +95,6 @@ android_library {
"SystemUIPluginLib",
"SystemUISharedLib",
"SettingsLib",
- "androidx.car_car",
"androidx.legacy_legacy-support-v4",
"androidx.recyclerview_recyclerview",
"androidx.preference_preference",
@@ -123,8 +119,6 @@ android_library {
libs: [
"android.test.runner",
"telephony-common",
- "android.car",
- "android.car.userlib",
"android.test.base",
],
aaptflags: [
@@ -149,8 +143,6 @@ android_app {
libs: [
"telephony-common",
- "android.car",
- "android.car.userlib",
],
dxflags: ["--multi-dex"],
@@ -158,6 +150,7 @@ android_app {
"--extra-packages",
"com.android.keyguard",
],
+ required: ["privapp_whitelist_com.android.systemui"],
}
@@ -186,8 +179,6 @@ android_app {
],
libs: [
"telephony-common",
- "android.car",
- "android.car.userlib",
],
srcs: [
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 7d53c2f78151..1c1a1404eacd 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -77,6 +77,7 @@
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.MASTER_CLEAR" />
<uses-permission android:name="android.permission.VIBRATE" />
+ <uses-permission android:name="android.permission.MANAGE_SENSOR_PRIVACY" />
<!-- ActivityManager -->
<uses-permission android:name="android.permission.REAL_GET_TASKS" />
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 89b873e7ffda..367a9ae28f97 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -32,7 +32,7 @@
android:letterSpacing="0.03"
android:textColor="?attr/wallpaperTextColor"
android:singleLine="true"
- style="@style/widget_big_thin"
+ style="@style/widget_big"
android:format12Hour="@string/keyguard_widget_12_hours_format"
android:format24Hour="@string/keyguard_widget_24_hours_format" />
</com.android.keyguard.KeyguardClockSwitch>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
index 32a7147cba85..67ecf6f7b21a 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
@@ -44,7 +44,7 @@
android:paddingLeft="@dimen/logout_button_padding_horizontal"
android:paddingRight="@dimen/logout_button_padding_horizontal"
android:background="@drawable/logout_button_background"
- android:fontFamily="roboto-medium"
+ android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
android:textAllCaps="true"
android:textColor="?android:attr/textColorPrimary"
android:textSize="13sp"
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 9baeaaac9b38..ffc7b3cca357 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -50,7 +50,7 @@
</style>
<style name="Widget.TextView.NumPadKey.Klondike" parent="Widget.TextView.NumPadKey">
<item name="android:textSize">12sp</item>
- <item name="android:fontFamily">sans-serif</item>
+ <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
<item name="android:textColor">?attr/wallpaperTextColorSecondary</item>
<item name="android:paddingBottom">0dp</item>
</style>
@@ -59,10 +59,10 @@
<style name="widget_label">
<item name="android:textSize">@dimen/widget_label_font_size</item>
</style>
- <style name="widget_big_thin">
+ <style name="widget_big">
<item name="android:textSize">@dimen/widget_big_font_size</item>
<item name="android:paddingBottom">@dimen/bottom_text_spacing_digital</item>
- <item name="android:fontFamily">@*android:string/config_headlineFontFamilyLight</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:fontFeatureSettings">@*android:string/config_headlineFontFeatureSettings</item>
<item name="android:ellipsize">none</item>
</style>
@@ -93,7 +93,7 @@
<item name="android:gravity">center</item>
<item name="android:ellipsize">end</item>
<item name="android:maxLines">2</item>
- <item name="android:fontFamily">@*android:string/config_headlineFontFamilyLight</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
</style>
<style name="TextAppearance.Keyguard.Secondary">
diff --git a/packages/SystemUI/res/drawable/ic_5g_mobiledata.xml b/packages/SystemUI/res/drawable/ic_5g_mobiledata.xml
new file mode 100644
index 000000000000..2aa6e57f6f82
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_5g_mobiledata.xml
@@ -0,0 +1,27 @@
+<!--
+ Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="14dp"
+ android:height="17dp"
+ android:viewportWidth="14"
+ android:viewportHeight="17">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13.9,12.24l-0.22,0.27c-0.63,0.73 -1.55,1.1 -2.76,1.1c-1.08,0 -1.92,-0.36 -2.53,-1.07s-0.93,-1.72 -0.94,-3.02V7.56c0,-1.39 0.28,-2.44 0.84,-3.13s1.39,-1.04 2.51,-1.04c0.95,0 1.69,0.26 2.23,0.79s0.83,1.28 0.89,2.26h-1.25c-0.05,-0.62 -0.22,-1.1 -0.52,-1.45s-0.74,-0.52 -1.34,-0.52c-0.72,0 -1.24,0.23 -1.57,0.7S8.72,6.37 8.71,7.4v2.03c0,1 0.19,1.77 0.57,2.31c0.38,0.54 0.93,0.8 1.65,0.8c0.67,0 1.19,-0.16 1.54,-0.49l0.18,-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M1.15,8.47l0.43,-4.96h4.33v1.17H2.6L2.37,7.39C2.78,7.1 3.22,6.96 3.69,6.96c0.77,0 1.38,0.3 1.83,0.9s0.66,1.41 0.66,2.43c0,1.03 -0.24,1.84 -0.72,2.43S4.32,13.6 3.48,13.6c-0.75,0 -1.36,-0.24 -1.83,-0.73s-0.74,-1.16 -0.81,-2.02h1.13c0.07,0.57 0.23,1 0.49,1.29c0.26,0.29 0.59,0.43 1.01,0.43c0.47,0 0.84,-0.2 1.1,-0.61c0.26,-0.41 0.4,-0.96 0.4,-1.65c0,-0.65 -0.14,-1.18 -0.43,-1.59S3.88,8.09 3.4,8.09c-0.4,0 -0.72,0.1 -0.96,0.31L2.11,8.73L1.15,8.47z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_5g_plus_mobiledata.xml b/packages/SystemUI/res/drawable/ic_5g_plus_mobiledata.xml
new file mode 100644
index 000000000000..10bbcc7b3737
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_5g_plus_mobiledata.xml
@@ -0,0 +1,33 @@
+<!--
+ Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="22"
+ android:viewportHeight="17"
+ android:width="22dp"
+ android:height="17dp">
+ <group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M1.03 8.47l0.43-4.96h4.33v1.17H2.48L2.25 7.39C2.66 7.1 3.1 6.96 3.57 6.96c0.77 0 1.38 0.3 1.83 0.9 s0.66 1.41 0.66 2.43c0 1.03-0.24 1.84-0.72 2.43S4.2 13.6 3.36 13.6c-0.75 0-1.36-0.24-1.83-0.73s-0.74-1.16-0.81-2.02h1.13 c0.07 0.57 0.23 1 0.49 1.29s0.59 0.43 1.01 0.43c0.47 0 0.84-0.2 1.1-0.61c0.26-0.41 0.4-0.96 0.4-1.65 c0-0.65-0.14-1.18-0.43-1.59S3.76 8.09 3.28 8.09c-0.4 0-0.72 0.1-0.96 0.31L1.99 8.73L1.03 8.47z"/>
+ </group>
+ <group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M 18.93,5.74 L 18.93,3.39 L 17.63,3.39 L 17.63,5.74 L 15.28,5.74 L 15.28,7.04 L 17.63,7.04 L 17.63,9.39 L 18.93,9.39 L 18.93,7.04 L 21.28,7.04 L 21.28,5.74 z"/>
+ </group>
+ <path android:fillColor="#FF000000"
+ android:pathData="M13.78 12.24l-0.22 0.27c-0.63 0.73-1.55 1.1-2.76 1.1c-1.08 0-1.92-0.36-2.53-1.07s-0.93-1.72-0.94-3.02V7.56 c0-1.39 0.28-2.44 0.84-3.13s1.39-1.04 2.51-1.04c0.95 0 1.69 0.26 2.23 0.79s0.83 1.28 0.89 2.26h-1.25 c-0.05-0.62-0.22-1.1-0.52-1.45s-0.74-0.52-1.34-0.52c-0.72 0-1.24 0.23-1.57 0.7S8.6 6.37 8.59 7.4v2.03c0 1 0.19 1.77 0.57 2.31 c0.38 0.54 0.93 0.8 1.65 0.8c0.67 0 1.19-0.16 1.54-0.49l0.18-0.17V9.59h-1.82V8.52h3.07V12.24z"/>
+ </group>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_signal_sensors.xml b/packages/SystemUI/res/drawable/ic_signal_sensors.xml
new file mode 100644
index 000000000000..faaddf6473c4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_signal_sensors.xml
@@ -0,0 +1,28 @@
+<!--
+ Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector android:height="48dp" android:viewportHeight="5"
+ android:viewportWidth="5" android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#00000000"
+ android:pathData="m4.762,0.661 l-4.233,4.233"
+ android:strokeAlpha="1" android:strokeColor="#000000"
+ android:strokeLineCap="round" android:strokeLineJoin="miter" android:strokeWidth=".5"/>
+ <path android:fillColor="#00000000"
+ android:pathData="M0.265,2.778L1.058,2.778l0.529,-1.323 0.529,2.646 0.529,-3.175 0.529,2.646 0.529,-1.587 0.265,0.794h1.058"
+ android:strokeAlpha="1" android:strokeColor="#000000"
+ android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth=".33"/>
+</vector>
+
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 9e97cd8f4099..61efbd5c2248 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -111,7 +111,7 @@
<!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
<string name="quick_settings_tiles_stock" translatable="false">
- wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,work,cast,night
+ wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,work,cast,night,sensorprivacy
</string>
<!-- The tiles to display in QuickSettings -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 9917257bba8a..07375ad333ed 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -406,6 +406,12 @@
<!-- Content description of the data connection type LTE+. [CHAR LIMIT=NONE] -->
<string name="data_connection_lte_plus">LTE+</string>
+ <!-- Content description of the data connection type 5G. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g" translate="false">5G</string>
+
+ <!-- Content description of the data connection type 5G+. [CHAR LIMIT=NONE] -->
+ <string name="data_connection_5g_plus" translate="false">5G+</string>
+
<!-- Content description of the data connection type CDMA. [CHAR LIMIT=NONE] -->
<string name="data_connection_cdma">1X</string>
@@ -598,6 +604,10 @@
<string name="accessibility_quick_settings_data_saver_changed_off">Data Saver turned off.</string>
<!-- Announcement made when the Data Saver changes to on (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_quick_settings_data_saver_changed_on">Data Saver turned on.</string>
+ <!-- Announcement made when the Sensor Privacy changes to off (not shown on the screen). [CHAR LIMIT=NONE] -->
+ <string name="accessibility_quick_settings_sensor_privacy_changed_off">Sensor Privacy turned off.</string>
+ <!-- Announcement made when the Sensor Privacy changes to on (not shown on the screen). [CHAR LIMIT=NONE] -->
+ <string name="accessibility_quick_settings_sensor_privacy_changed_on">Sensor Privacy turned on.</string>
<!-- Content description of the display brightness slider (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_brightness">Display brightness</string>
@@ -2318,4 +2328,6 @@
<item quantity="one"><xliff:g id="num_apps" example="1">%d</xliff:g> other app</item>
<item quantity="other"><xliff:g id="num_apps" example="3">%d</xliff:g> other apps</item>
</plurals>
+ <!-- Text for the quick setting tile for sensor privacy [CHAR LIMIT=30] -->
+ <string name="sensor_privacy_mode">Sensors off</string>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 87155c4d41a2..8a5a69b61a43 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -125,7 +125,7 @@
<style name="TextAppearance.StatusBar.Clock" parent="@*android:style/TextAppearance.StatusBar.Icon">
<item name="android:textSize">@dimen/status_bar_clock_size</item>
- <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+ <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
<item name="android:textColor">@color/status_bar_clock_color</item>
</style>
diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
index d3dded0e25b2..b21bcc98ae68 100644
--- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
@@ -137,7 +137,7 @@ public class PasswordTextView extends View {
mDrawPaint.setFlags(Paint.SUBPIXEL_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG);
mDrawPaint.setTextAlign(Paint.Align.CENTER);
mDrawPaint.setTypeface(Typeface.create(
- context.getString(com.android.internal.R.string.config_headlineFontFamilyLight),
+ context.getString(com.android.internal.R.string.config_headlineFontFamily),
0));
mShowPassword = Settings.System.getInt(mContext.getContentResolver(),
Settings.System.TEXT_SHOW_PASSWORD, 1) == 1;
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 5e6d272bd427..327ffcd24762 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -17,6 +17,7 @@ package com.android.systemui;
import android.content.Context;
import android.content.res.Configuration;
import android.hardware.SensorManager;
+import android.hardware.SensorPrivacyManager;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
@@ -191,6 +192,9 @@ public class Dependency extends SystemUI {
new AsyncSensorManager(mContext.getSystemService(SensorManager.class),
getDependency(PluginManager.class)));
+ mProviders.put(SensorPrivacyManager.class, () ->
+ mContext.getSystemService(SensorPrivacyManager.class));
+
mProviders.put(BluetoothController.class, () ->
new BluetoothControllerImpl(mContext, getDependency(BG_LOOPER)));
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 7d52f0b2763d..fd2c4e35e49b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -41,6 +41,7 @@ import com.android.systemui.qs.tiles.LocationTile;
import com.android.systemui.qs.tiles.NfcTile;
import com.android.systemui.qs.tiles.NightDisplayTile;
import com.android.systemui.qs.tiles.RotationLockTile;
+import com.android.systemui.qs.tiles.SensorPrivacyTile;
import com.android.systemui.qs.tiles.UserTile;
import com.android.systemui.qs.tiles.WifiTile;
import com.android.systemui.qs.tiles.WorkModeTile;
@@ -100,6 +101,8 @@ public class QSFactoryImpl implements QSFactory {
return new NightDisplayTile(mHost);
case "nfc":
return new NfcTile(mHost);
+ case "sensorprivacy":
+ return new SensorPrivacyTile(mHost);
}
// Intent tiles.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyTile.java
new file mode 100644
index 000000000000..ff368f80d06a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyTile.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles;
+
+import android.content.Intent;
+import android.hardware.SensorPrivacyManager;
+import android.service.quicksettings.Tile;
+import android.widget.Switch;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.qs.QSTile.BooleanState;
+import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
+
+/** Quick settings tile: SensorPrivacy mode **/
+public class SensorPrivacyTile extends QSTileImpl<BooleanState> implements
+ SensorPrivacyManager.OnSensorPrivacyChangedListener {
+ private static final String TAG = "SensorPrivacy";
+ private final Icon mIcon =
+ ResourceIcon.get(R.drawable.ic_signal_sensors);
+ private final KeyguardMonitor mKeyguard;
+ private final SensorPrivacyManager mSensorPrivacyManager;
+
+ public SensorPrivacyTile(QSHost host) {
+ super(host);
+
+ mSensorPrivacyManager = Dependency.get(SensorPrivacyManager.class);
+ mKeyguard = Dependency.get(KeyguardMonitor.class);
+ }
+
+ @Override
+ public BooleanState newTileState() {
+ return new BooleanState();
+ }
+
+ @Override
+ public void handleClick() {
+ final boolean wasEnabled = mState.value;
+ // Don't allow disabling from the lockscreen.
+ if (wasEnabled && mKeyguard.isSecure() && mKeyguard.isShowing()) {
+ Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() -> {
+ MetricsLogger.action(mContext, getMetricsCategory(), !wasEnabled);
+ setEnabled(!wasEnabled);
+ });
+ return;
+ }
+
+ MetricsLogger.action(mContext, getMetricsCategory(), !wasEnabled);
+ setEnabled(!wasEnabled);
+ }
+
+ private void setEnabled(boolean enabled) {
+ mSensorPrivacyManager.setSensorPrivacy(enabled);
+ }
+
+ @Override
+ public CharSequence getTileLabel() {
+ return mContext.getString(R.string.sensor_privacy_mode);
+ }
+
+ @Override
+ public Intent getLongClickIntent() {
+ return null;
+ }
+
+ @Override
+ protected void handleUpdateState(BooleanState state, Object arg) {
+ final boolean enabled = arg instanceof Boolean ? (Boolean) arg
+ : mSensorPrivacyManager.isSensorPrivacyEnabled();
+ state.value = enabled;
+ state.label = mContext.getString(R.string.sensor_privacy_mode);
+ state.icon = mIcon;
+ state.state = enabled ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
+ state.contentDescription = state.label;
+ state.expandedAccessibilityClassName = Switch.class.getName();
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsEvent.QS_SENSOR_PRIVACY;
+ }
+
+ @Override
+ protected String composeChangeAnnouncement() {
+ if (mState.value) {
+ return mContext
+ .getString(R.string.accessibility_quick_settings_sensor_privacy_changed_on);
+ } else {
+ return mContext
+ .getString(R.string.accessibility_quick_settings_sensor_privacy_changed_off);
+ }
+ }
+
+ @Override
+ protected void handleSetListening(boolean listening) {
+ if (listening) {
+ mSensorPrivacyManager.addSensorPrivacyListener(this);
+ } else {
+ mSensorPrivacyManager.removeSensorPrivacyListener(this);
+ }
+ }
+
+ @Override
+ public void onSensorPrivacyChanged(boolean enabled) {
+ refreshState(enabled);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index dc3a60786ce2..0702f1b9f1fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -437,7 +437,7 @@ public class NotificationViewHierarchyManager {
}
row.showAppOpsIcons(entry.mActiveAppOps);
- row.setAudiblyAlerted(entry.audiblyAlerted);
+ row.setLastAudiblyAlertedMs(entry.lastAudiblyAlertedMs);
}
Trace.beginSection("NotificationPresenter#onUpdateRowStates");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AlertTransferListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AlertTransferListener.java
new file mode 100644
index 000000000000..13e991ba2431
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AlertTransferListener.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification;
+
+/**
+ * Listener interface for when NotificationEntryManager needs to tell
+ * NotificationGroupAlertTransferHelper things. Will eventually grow to be a general-purpose
+ * listening interface for the NotificationEntryManager.
+ */
+public interface AlertTransferListener {
+ /**
+ * Called when a new notification is posted. At this point, the notification is "pending": its
+ * views haven't been inflated yet and most of the system pretends like it doesn't exist yet.
+ */
+ void onPendingEntryAdded(NotificationData.Entry entry);
+
+ /**
+ * Called when an existing notification's views are reinflated (usually due to an update being
+ * posted to that notification).
+ */
+ void onEntryReinflated(NotificationData.Entry entry);
+
+ /**
+ * Called when a notification has been removed (either because the user swiped it away or
+ * because the developer retracted it).
+ */
+ void onEntryRemoved(NotificationData.Entry entry);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java
index f543b4622611..ae9f323c2ebf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java
@@ -103,7 +103,7 @@ public class NotificationData {
public String key;
public StatusBarNotification notification;
public NotificationChannel channel;
- public boolean audiblyAlerted;
+ public long lastAudiblyAlertedMs;
public boolean noisy;
public int importance;
public StatusBarIconView icon;
@@ -172,7 +172,7 @@ public class NotificationData {
public void populateFromRanking(@NonNull Ranking ranking) {
channel = ranking.getChannel();
- audiblyAlerted = ranking.audiblyAlerted();
+ lastAudiblyAlertedMs = ranking.getLastAudiblyAlertedMillis();
importance = ranking.getImportance();
snoozeCriteria = ranking.getSnoozeCriteria();
userSentiment = ranking.getUserSentiment();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 16a3849a6eec..aab3fc4c7a0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -32,6 +32,7 @@ import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.os.Build;
import android.os.Bundle;
+import android.os.Handler;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -61,7 +62,6 @@ import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.EventLogTags;
import com.android.systemui.ForegroundServiceController;
-import com.android.systemui.InitController;
import com.android.systemui.R;
import com.android.systemui.UiOffloadThread;
import com.android.systemui.bubbles.BubbleController;
@@ -83,7 +83,6 @@ import com.android.systemui.statusbar.notification.row.NotificationInflater;
import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
import com.android.systemui.statusbar.notification.row.RowInflaterTask;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -96,6 +95,7 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.concurrent.TimeUnit;
/**
* NotificationEntryManager is responsible for the adding, removing, and updating of notifications.
@@ -110,6 +110,8 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
private static final boolean ENABLE_HEADS_UP = true;
private static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
+ public static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
+
private final NotificationMessagingUtil mMessagingUtil;
protected final Context mContext;
protected final HashMap<String, NotificationData.Entry> mPendingNotifications = new HashMap<>();
@@ -117,8 +119,6 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
private final NotificationGroupManager mGroupManager =
Dependency.get(NotificationGroupManager.class);
- private final NotificationGroupAlertTransferHelper mGroupAlertTransferHelper =
- Dependency.get(NotificationGroupAlertTransferHelper.class);
private final NotificationGutsManager mGutsManager =
Dependency.get(NotificationGutsManager.class);
private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
@@ -139,6 +139,9 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
private NotificationListener mNotificationListener;
private ShadeController mShadeController;
+ private final Handler mDeferredNotificationViewUpdateHandler;
+ private Runnable mUpdateNotificationViewsCallback;
+
protected IDreamManager mDreamManager;
protected IStatusBarService mBarService;
private NotificationPresenter mPresenter;
@@ -157,6 +160,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
= new ArrayList<>();
private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener;
private NotificationViewHierarchyManager.StatusBarStateListener mStatusBarStateListener;
+ @Nullable private AlertTransferListener mAlertTransferListener;
private final class NotificationClicker implements View.OnClickListener {
@@ -258,12 +262,11 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
mMessagingUtil = new NotificationMessagingUtil(context);
mBubbleController.setDismissListener(this /* bubbleEventListener */);
mNotificationData = new NotificationData();
- Dependency.get(InitController.class).addPostInitTask(this::onPostInit);
+ mDeferredNotificationViewUpdateHandler = new Handler();
}
- private void onPostInit() {
- mGroupAlertTransferHelper.setPendingEntries(mPendingNotifications);
- mGroupManager.addOnGroupChangeListener(mGroupAlertTransferHelper);
+ public void setAlertTransferListener(AlertTransferListener listener) {
+ mAlertTransferListener = listener;
}
/**
@@ -301,6 +304,7 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
NotificationListContainer listContainer, Callback callback,
HeadsUpManager headsUpManager) {
mPresenter = presenter;
+ mUpdateNotificationViewsCallback = mPresenter::updateNotificationViews;
mCallback = callback;
mHeadsUpManager = headsUpManager;
mNotificationData.setHeadsUpManager(mHeadsUpManager);
@@ -540,6 +544,17 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
tagForeground(shadeEntry.notification);
updateNotifications();
mCallback.onNotificationAdded(shadeEntry);
+
+ maybeScheduleUpdateNotificationViews(shadeEntry);
+ }
+
+ private void maybeScheduleUpdateNotificationViews(NotificationData.Entry entry) {
+ long audibleAlertTimeout = RECENTLY_ALERTED_THRESHOLD_MS
+ - (System.currentTimeMillis() - entry.lastAudiblyAlertedMs);
+ if (audibleAlertTimeout > 0) {
+ mDeferredNotificationViewUpdateHandler.postDelayed(
+ mUpdateNotificationViewsCallback, audibleAlertTimeout);
+ }
}
/**
@@ -587,7 +602,9 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
mVisualStabilityManager.onLowPriorityUpdated(entry);
mPresenter.updateNotificationViews();
}
- mGroupAlertTransferHelper.onInflationFinished(entry);
+ if (mAlertTransferListener != null) {
+ mAlertTransferListener.onEntryReinflated(entry);
+ }
}
}
entry.setLowPriorityStateUpdated(false);
@@ -600,8 +617,12 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
private void removeNotificationInternal(String key,
@Nullable NotificationListenerService.RankingMap ranking, boolean forceRemove) {
+ final NotificationData.Entry entry = mNotificationData.get(key);
+
abortExistingInflation(key);
- mGroupAlertTransferHelper.cleanUpPendingAlertInfo(key);
+ if (mAlertTransferListener != null && entry != null) {
+ mAlertTransferListener.onEntryRemoved(entry);
+ }
// Attempt to remove notifications from their alert managers (heads up, ambient pulse).
// Though the remove itself may fail, it lets the manager know to remove as soon as
@@ -620,8 +641,6 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
mAmbientPulseManager.removeNotification(key, false /* ignoreEarliestRemovalTime */);
}
- NotificationData.Entry entry = mNotificationData.get(key);
-
if (entry == null) {
mCallback.onNotificationRemoved(key, null /* old */);
return;
@@ -846,7 +865,9 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
mNotificationData.getImportance(key));
mPendingNotifications.put(key, shadeEntry);
- mGroupAlertTransferHelper.onPendingEntryAdded(shadeEntry);
+ if (mAlertTransferListener != null) {
+ mAlertTransferListener.onPendingEntryAdded(shadeEntry);
+ }
}
@VisibleForTesting
@@ -937,6 +958,8 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
}
mCallback.onNotificationUpdated(notification);
+
+ maybeScheduleUpdateNotificationViews(entry);
}
@Override
@@ -1231,6 +1254,15 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
}
/**
+ * @return An iterator for all "pending" notifications. Pending notifications are newly-posted
+ * notifications whose views have not yet been inflated. In general, the system pretends like
+ * these don't exist, although there are a couple exceptions.
+ */
+ public Iterable<NotificationData.Entry> getPendingNotificationsIterator() {
+ return mPendingNotifications.values();
+ }
+
+ /**
* Callback for NotificationEntryManager.
*/
public interface Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 383446c00d37..91d08fffa642 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -87,6 +87,7 @@ import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.NotificationData;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.logging.NotificationCounters;
@@ -1689,14 +1690,17 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mPublicLayout.showAppOpsIcons(activeOps);
}
- /** Sets whether the notification being displayed audibly alerted the user. */
- public void setAudiblyAlerted(boolean audiblyAlerted) {
+ /** Sets the last time the notification being displayed audibly alerted the user. */
+ public void setLastAudiblyAlertedMs(long lastAudiblyAlertedMs) {
if (NotificationUtils.useNewInterruptionModel(mContext)) {
+ boolean recentlyAudiblyAlerted = System.currentTimeMillis() - lastAudiblyAlertedMs
+ < NotificationEntryManager.RECENTLY_ALERTED_THRESHOLD_MS;
if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() != null) {
- mChildrenContainer.getHeaderView().setAudiblyAlerted(audiblyAlerted);
+ mChildrenContainer.getHeaderView().setRecentlyAudiblyAlerted(
+ recentlyAudiblyAlerted);
}
- mPrivateLayout.setAudiblyAlerted(audiblyAlerted);
- mPublicLayout.setAudiblyAlerted(audiblyAlerted);
+ mPrivateLayout.setRecentlyAudiblyAlerted(recentlyAudiblyAlerted);
+ mPublicLayout.setRecentlyAudiblyAlerted(recentlyAudiblyAlerted);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index edd54ca936fa..6bc39c82f8bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -1615,15 +1615,15 @@ public class NotificationContentView extends FrameLayout {
}
/** Sets whether the notification being displayed audibly alerted the user. */
- public void setAudiblyAlerted(boolean audiblyAlerted) {
+ public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) {
if (mContractedChild != null && mContractedWrapper.getNotificationHeader() != null) {
- mContractedWrapper.getNotificationHeader().setAudiblyAlerted(audiblyAlerted);
+ mContractedWrapper.getNotificationHeader().setRecentlyAudiblyAlerted(audiblyAlerted);
}
if (mExpandedChild != null && mExpandedWrapper.getNotificationHeader() != null) {
- mExpandedWrapper.getNotificationHeader().setAudiblyAlerted(audiblyAlerted);
+ mExpandedWrapper.getNotificationHeader().setRecentlyAudiblyAlerted(audiblyAlerted);
}
if (mHeadsUpChild != null && mHeadsUpWrapper.getNotificationHeader() != null) {
- mHeadsUpWrapper.getNotificationHeader().setAudiblyAlerted(audiblyAlerted);
+ mHeadsUpWrapper.getNotificationHeader().setRecentlyAudiblyAlerted(audiblyAlerted);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
index 2a68fa598603..dd81c4e20d6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
@@ -29,7 +29,9 @@ import com.android.systemui.statusbar.AmbientPulseManager.OnAmbientChangedListen
import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.statusbar.notification.AlertTransferListener;
import com.android.systemui.statusbar.notification.NotificationData.Entry;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.row.NotificationInflater.AsyncInflationTask;
import com.android.systemui.statusbar.notification.row.NotificationInflater.InflationFlag;
import com.android.systemui.statusbar.phone.NotificationGroupManager.NotificationGroup;
@@ -38,8 +40,6 @@ import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
import java.util.Objects;
/**
@@ -47,8 +47,8 @@ import java.util.Objects;
* {@link HeadsUpManager}, {@link AmbientPulseManager}. In particular, this class deals with keeping
* the correct notification in a group alerting based off the group suppression.
*/
-public class NotificationGroupAlertTransferHelper implements OnGroupChangeListener,
- OnHeadsUpChangedListener, OnAmbientChangedListener, StateListener {
+public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedListener,
+ OnAmbientChangedListener, StateListener {
private static final long ALERT_TRANSFER_TIMEOUT = 300;
@@ -69,15 +69,7 @@ public class NotificationGroupAlertTransferHelper implements OnGroupChangeListen
private final NotificationGroupManager mGroupManager =
Dependency.get(NotificationGroupManager.class);
- // TODO(b/119637830): It would be good if GroupManager already had all pending notifications as
- // normal children (i.e. add notifications to GroupManager before inflation) so that we don't
- // have to have this dependency. We'd also have to worry less about the suppression not being up
- // to date.
- /**
- * Notifications that are currently inflating for the first time. Used to remove an incorrectly
- * alerting notification faster.
- */
- private HashMap<String, Entry> mPendingNotifications;
+ private NotificationEntryManager mEntryManager;
private boolean mIsDozing;
@@ -85,6 +77,23 @@ public class NotificationGroupAlertTransferHelper implements OnGroupChangeListen
Dependency.get(StatusBarStateController.class).addCallback(this);
}
+ /** Causes the TransferHelper to register itself as a listener to the appropriate classes. */
+ public void bind(NotificationEntryManager entryManager,
+ NotificationGroupManager groupManager) {
+ if (mEntryManager != null) {
+ throw new IllegalStateException("Already bound.");
+ }
+
+ // TODO(b/119637830): It would be good if GroupManager already had all pending notifications
+ // as normal children (i.e. add notifications to GroupManager before inflation) so that we
+ // don't have to have this dependency. We'd also have to worry less about the suppression
+ // not being up to date.
+ mEntryManager = entryManager;
+
+ mEntryManager.setAlertTransferListener(mAlertTransferListener);
+ groupManager.addOnGroupChangeListener(mOnGroupChangeListener);
+ }
+
/**
* Whether or not a notification has transferred its alert state to the notification and
* the notification should alert after inflating.
@@ -97,25 +106,10 @@ public class NotificationGroupAlertTransferHelper implements OnGroupChangeListen
return alertInfo != null && alertInfo.isStillValid();
}
- /**
- * Removes any alerts pending on this entry. Note that this will not stop any inflation tasks
- * started by a transfer, so this should only be used as clean-up for when inflation is stopped
- * and the pending alert no longer needs to happen.
- *
- * @param key notification key that may have info that needs to be cleaned up
- */
- public void cleanUpPendingAlertInfo(@NonNull String key) {
- mPendingAlerts.remove(key);
- }
-
public void setHeadsUpManager(HeadsUpManager headsUpManager) {
mHeadsUpManager = headsUpManager;
}
- public void setPendingEntries(HashMap<String, Entry> pendingNotifications) {
- mPendingNotifications = pendingNotifications;
- }
-
@Override
public void onStateChanged(int newState) {}
@@ -130,43 +124,45 @@ public class NotificationGroupAlertTransferHelper implements OnGroupChangeListen
mIsDozing = isDozing;
}
- @Override
- public void onGroupCreated(NotificationGroup group, String groupKey) {
- mGroupAlertEntries.put(groupKey, new GroupAlertEntry(group));
- }
+ private final OnGroupChangeListener mOnGroupChangeListener = new OnGroupChangeListener() {
+ @Override
+ public void onGroupCreated(NotificationGroup group, String groupKey) {
+ mGroupAlertEntries.put(groupKey, new GroupAlertEntry(group));
+ }
- @Override
- public void onGroupRemoved(NotificationGroup group, String groupKey) {
- mGroupAlertEntries.remove(groupKey);
- }
+ @Override
+ public void onGroupRemoved(NotificationGroup group, String groupKey) {
+ mGroupAlertEntries.remove(groupKey);
+ }
- @Override
- public void onGroupSuppressionChanged(NotificationGroup group, boolean suppressed) {
- AlertingNotificationManager alertManager = getActiveAlertManager();
- if (suppressed) {
- if (alertManager.isAlerting(group.summary.key)) {
- handleSuppressedSummaryAlerted(group.summary, alertManager);
- }
- } else {
- // Group summary can be null if we are no longer suppressed because the summary was
- // removed. In that case, we don't need to alert the summary.
- if (group.summary == null) {
- return;
- }
- GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(mGroupManager.getGroupKey(
- group.summary.notification));
- // Group is no longer suppressed. We should check if we need to transfer the alert
- // back to the summary now that it's no longer suppressed.
- if (groupAlertEntry.mAlertSummaryOnNextAddition) {
- if (!alertManager.isAlerting(group.summary.key)) {
- alertNotificationWhenPossible(group.summary, alertManager);
+ @Override
+ public void onGroupSuppressionChanged(NotificationGroup group, boolean suppressed) {
+ AlertingNotificationManager alertManager = getActiveAlertManager();
+ if (suppressed) {
+ if (alertManager.isAlerting(group.summary.key)) {
+ handleSuppressedSummaryAlerted(group.summary, alertManager);
}
- groupAlertEntry.mAlertSummaryOnNextAddition = false;
} else {
- checkShouldTransferBack(groupAlertEntry);
+ // Group summary can be null if we are no longer suppressed because the summary was
+ // removed. In that case, we don't need to alert the summary.
+ if (group.summary == null) {
+ return;
+ }
+ GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(mGroupManager.getGroupKey(
+ group.summary.notification));
+ // Group is no longer suppressed. We should check if we need to transfer the alert
+ // back to the summary now that it's no longer suppressed.
+ if (groupAlertEntry.mAlertSummaryOnNextAddition) {
+ if (!alertManager.isAlerting(group.summary.key)) {
+ alertNotificationWhenPossible(group.summary, alertManager);
+ }
+ groupAlertEntry.mAlertSummaryOnNextAddition = false;
+ } else {
+ checkShouldTransferBack(groupAlertEntry);
+ }
}
}
- }
+ };
@Override
public void onAmbientStateChanged(Entry entry, boolean isAmbient) {
@@ -185,37 +181,42 @@ public class NotificationGroupAlertTransferHelper implements OnGroupChangeListen
}
}
- /**
- * Called when the entry's reinflation has finished. If there is an alert pending, we then
- * show the alert.
- *
- * @param entry entry whose inflation has finished
- */
- public void onInflationFinished(@NonNull Entry entry) {
- PendingAlertInfo alertInfo = mPendingAlerts.remove(entry.key);
- if (alertInfo != null) {
- if (alertInfo.isStillValid()) {
- alertNotificationWhenPossible(entry, getActiveAlertManager());
- } else {
- // The transfer is no longer valid. Free the content.
- entry.getRow().freeContentViewWhenSafe(alertInfo.mAlertManager.getContentFlag());
+ private final AlertTransferListener mAlertTransferListener = new AlertTransferListener() {
+ // Called when a new notification has been posted but is not inflated yet. We use this to
+ // see as early as we can if we need to abort a transfer.
+ @Override
+ public void onPendingEntryAdded(Entry entry) {
+ String groupKey = mGroupManager.getGroupKey(entry.notification);
+ GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(groupKey);
+ if (groupAlertEntry != null) {
+ checkShouldTransferBack(groupAlertEntry);
}
}
- }
- /**
- * Called when a new notification has been posted but is not inflated yet. We use this to see
- * as early as we can if we need to abort a transfer.
- *
- * @param entry entry that has been added
- */
- public void onPendingEntryAdded(@NonNull Entry entry) {
- String groupKey = mGroupManager.getGroupKey(entry.notification);
- GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(groupKey);
- if (groupAlertEntry != null) {
- checkShouldTransferBack(groupAlertEntry);
+ // Called when the entry's reinflation has finished. If there is an alert pending, we
+ // then show the alert.
+ @Override
+ public void onEntryReinflated(Entry entry) {
+ PendingAlertInfo alertInfo = mPendingAlerts.remove(entry.key);
+ if (alertInfo != null) {
+ if (alertInfo.isStillValid()) {
+ alertNotificationWhenPossible(entry, getActiveAlertManager());
+ } else {
+ // The transfer is no longer valid. Free the content.
+ entry.getRow().freeContentViewWhenSafe(
+ alertInfo.mAlertManager.getContentFlag());
+ }
+ }
}
- }
+
+ @Override
+ public void onEntryRemoved(Entry entry) {
+ // Removes any alerts pending on this entry. Note that this will not stop any inflation
+ // tasks started by a transfer, so this should only be used as clean-up for when
+ // inflation is stopped and the pending alert no longer needs to happen.
+ mPendingAlerts.remove(entry.key);
+ }
+ };
/**
* Gets the number of new notifications pending inflation that will be added to the group
@@ -225,11 +226,11 @@ public class NotificationGroupAlertTransferHelper implements OnGroupChangeListen
* @return the number of new notifications that will be added to the group
*/
private int getPendingChildrenNotAlerting(@NonNull NotificationGroup group) {
- if (mPendingNotifications == null) {
+ if (mEntryManager == null) {
return 0;
}
int number = 0;
- Collection<Entry> values = mPendingNotifications.values();
+ Iterable<Entry> values = mEntryManager.getPendingNotificationsIterator();
for (Entry entry : values) {
if (isPendingNotificationInGroup(entry, group) && onlySummaryAlerts(entry)) {
number++;
@@ -245,10 +246,10 @@ public class NotificationGroupAlertTransferHelper implements OnGroupChangeListen
* @return true if a pending notification will add to this group
*/
private boolean pendingInflationsWillAddChildren(@NonNull NotificationGroup group) {
- if (mPendingNotifications == null) {
+ if (mEntryManager == null) {
return false;
}
- Collection<Entry> values = mPendingNotifications.values();
+ Iterable<Entry> values = mEntryManager.getPendingNotificationsIterator();
for (Entry entry : values) {
if (isPendingNotificationInGroup(entry, group)) {
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 1d6a1e81246c..a508f1bdbd34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -628,6 +628,8 @@ public class StatusBar extends SystemUI implements DemoMode,
mBubbleController = Dependency.get(BubbleController.class);
mBubbleController.setExpandListener(mBubbleExpandListener);
+ mGroupAlertTransferHelper.bind(mEntryManager, mGroupManager);
+
mColorExtractor.addOnColorsChangedListener(this);
mStatusBarStateController.addCallback(this, StatusBarStateController.RANK_STATUS_BAR);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index e943261bda7b..3deede091a05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -22,6 +22,7 @@ import android.net.NetworkCapabilities;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings.Global;
+import android.telephony.NetworkRegistrationState;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
@@ -437,7 +438,13 @@ public class MobileSignalController extends SignalController<
mCurrentState.level = mSignalStrength.getLevel();
}
}
- if (mNetworkToIconLookup.indexOfKey(mDataNetType) >= 0) {
+
+ // When the device is camped on a 5G Non-Standalone network, the data network type is still
+ // LTE. In this case, we first check which 5G icon should be shown.
+ MobileIconGroup nr5GIconGroup = getNr5GIconGroup();
+ if (nr5GIconGroup != null) {
+ mCurrentState.iconGroup = nr5GIconGroup;
+ } else if (mNetworkToIconLookup.indexOfKey(mDataNetType) >= 0) {
mCurrentState.iconGroup = mNetworkToIconLookup.get(mDataNetType);
} else {
mCurrentState.iconGroup = mDefaultIcons;
@@ -464,6 +471,36 @@ public class MobileSignalController extends SignalController<
notifyListenersIfNecessary();
}
+ private MobileIconGroup getNr5GIconGroup() {
+ if (mServiceState == null) return null;
+
+ int nrStatus = mServiceState.getNrStatus();
+ if (nrStatus == NetworkRegistrationState.NR_STATUS_CONNECTED) {
+ // Check if the NR 5G is using millimeter wave and the icon is config.
+ if (mServiceState.getNrFrequencyRange() == ServiceState.FREQUENCY_RANGE_MMWAVE) {
+ if (mConfig.nr5GIconMap.containsKey(Config.NR_CONNECTED_MMWAVE)) {
+ return mConfig.nr5GIconMap.get(Config.NR_CONNECTED_MMWAVE);
+ }
+ }
+
+ // If NR 5G is not using millimeter wave or there is no icon for millimeter wave, we
+ // check the normal 5G icon.
+ if (mConfig.nr5GIconMap.containsKey(Config.NR_CONNECTED)) {
+ return mConfig.nr5GIconMap.get(Config.NR_CONNECTED);
+ }
+ } else if (nrStatus == NetworkRegistrationState.NR_STATUS_NOT_RESTRICTED) {
+ if (mConfig.nr5GIconMap.containsKey(Config.NR_NOT_RESTRICTED)) {
+ return mConfig.nr5GIconMap.get(Config.NR_NOT_RESTRICTED);
+ }
+ } else if (nrStatus == NetworkRegistrationState.NR_STATUS_RESTRICTED) {
+ if (mConfig.nr5GIconMap.containsKey(Config.NR_RESTRICTED)) {
+ return mConfig.nr5GIconMap.get(Config.NR_RESTRICTED);
+ }
+ }
+
+ return null;
+ }
+
private boolean isDataDisabled() {
return !mPhone.getDataEnabled(mSubscriptionInfo.getSubscriptionId());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 70a35892da98..bc43120fcefa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -60,15 +60,19 @@ import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
+import com.android.systemui.statusbar.policy.MobileSignalController.MobileIconGroup;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.BitSet;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
/** Platform implementation of the network controller. **/
public class NetworkControllerImpl extends BroadcastReceiver
@@ -1029,6 +1033,13 @@ public class NetworkControllerImpl extends BroadcastReceiver
@VisibleForTesting
static class Config {
+ static final int NR_CONNECTED_MMWAVE = 1;
+ static final int NR_CONNECTED = 2;
+ static final int NR_NOT_RESTRICTED = 3;
+ static final int NR_RESTRICTED = 4;
+
+ Map<Integer, MobileIconGroup> nr5GIconMap = new HashMap<>();
+
boolean showAtLeast3G = false;
boolean alwaysShowCdmaRssi = false;
boolean show4gForLte = false;
@@ -1037,6 +1048,19 @@ public class NetworkControllerImpl extends BroadcastReceiver
boolean inflateSignalStrengths = false;
boolean alwaysShowDataRatIcon = false;
+ /**
+ * Mapping from NR 5G status string to an integer. The NR 5G status string should match
+ * those in carrier config.
+ */
+ private static final Map<String, Integer> NR_STATUS_STRING_TO_INDEX;
+ static {
+ NR_STATUS_STRING_TO_INDEX = new HashMap<>(4);
+ NR_STATUS_STRING_TO_INDEX.put("connected_mmwave", NR_CONNECTED_MMWAVE);
+ NR_STATUS_STRING_TO_INDEX.put("connected", NR_CONNECTED);
+ NR_STATUS_STRING_TO_INDEX.put("not_restricted", NR_NOT_RESTRICTED);
+ NR_STATUS_STRING_TO_INDEX.put("restricted", NR_RESTRICTED);
+ }
+
static Config readConfig(Context context) {
Config config = new Config();
Resources res = context.getResources();
@@ -1061,8 +1085,46 @@ public class NetworkControllerImpl extends BroadcastReceiver
CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL);
config.hideLtePlus = b.getBoolean(
CarrierConfigManager.KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL);
+ String nr5GIconConfiguration =
+ b.getString(CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING);
+ if (!TextUtils.isEmpty(nr5GIconConfiguration)) {
+ String[] nr5GIconConfigPairs = nr5GIconConfiguration.trim().split(",");
+ for (String pair : nr5GIconConfigPairs) {
+ add5GIconMapping(pair, config);
+ }
+ }
}
+
return config;
}
+
+ /**
+ * Add a mapping from NR 5G status to the 5G icon. All the icon resources come from
+ * {@link TelephonyIcons}.
+ *
+ * @param keyValuePair the NR 5G status and icon name separated by a colon.
+ * @param config container that used to store the parsed configs.
+ */
+ @VisibleForTesting
+ static void add5GIconMapping(String keyValuePair, Config config) {
+ String[] kv = (keyValuePair.trim().toLowerCase()).split(":");
+
+ if (kv.length != 2) {
+ if (DEBUG) Log.e(TAG, "Invalid 5G icon configuration, config = " + keyValuePair);
+ return;
+ }
+
+ String key = kv[0], value = kv[1];
+
+ // There is no icon config for the specific 5G status.
+ if (value.equals("none")) return;
+
+ if (NR_STATUS_STRING_TO_INDEX.containsKey(key)
+ && TelephonyIcons.ICON_NAME_TO_ICON.containsKey(value)) {
+ config.nr5GIconMap.put(
+ NR_STATUS_STRING_TO_INDEX.get(key),
+ TelephonyIcons.ICON_NAME_TO_ICON.get(value));
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
index bd768202aa7a..7347f66de8ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
@@ -19,6 +19,9 @@ package com.android.systemui.statusbar.policy;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.MobileSignalController.MobileIconGroup;
+import java.util.HashMap;
+import java.util.Map;
+
class TelephonyIcons {
//***** Data connection icons
static final int FLIGHT_MODE_ICON = R.drawable.stat_sys_airplane_mode;
@@ -33,6 +36,8 @@ class TelephonyIcons {
static final int ICON_4G = R.drawable.ic_4g_mobiledata;
static final int ICON_4G_PLUS = R.drawable.ic_4g_plus_mobiledata;
static final int ICON_1X = R.drawable.ic_1x_mobiledata;
+ static final int ICON_5G = R.drawable.ic_5g_mobiledata;
+ static final int ICON_5G_PLUS = R.drawable.ic_5g_plus_mobiledata;
static final MobileIconGroup CARRIER_NETWORK_CHANGE = new MobileIconGroup(
"CARRIER_NETWORK_CHANGE",
@@ -199,6 +204,34 @@ class TelephonyIcons {
TelephonyIcons.ICON_LTE_PLUS,
true);
+ static final MobileIconGroup NR_5G = new MobileIconGroup(
+ "5G",
+ null,
+ null,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
+ 0,
+ 0,
+ 0,
+ 0,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
+ R.string.data_connection_5g,
+ TelephonyIcons.ICON_5G,
+ true);
+
+ static final MobileIconGroup NR_5G_PLUS = new MobileIconGroup(
+ "5G_PLUS",
+ null,
+ null,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
+ 0,
+ 0,
+ 0,
+ 0,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
+ R.string.data_connection_5g_plus,
+ TelephonyIcons.ICON_5G_PLUS,
+ true);
+
static final MobileIconGroup DATA_DISABLED = new MobileIconGroup(
"DataDisabled",
null,
@@ -211,5 +244,27 @@ class TelephonyIcons {
R.string.cell_data_off_content_description,
0,
false);
+
+ /** Mapping icon name(lower case) to the icon object. */
+ static final Map<String, MobileIconGroup> ICON_NAME_TO_ICON;
+ static {
+ ICON_NAME_TO_ICON = new HashMap<>();
+ ICON_NAME_TO_ICON.put("carrier_network_change", CARRIER_NETWORK_CHANGE);
+ ICON_NAME_TO_ICON.put("3g", THREE_G);
+ ICON_NAME_TO_ICON.put("wfc", WFC);
+ ICON_NAME_TO_ICON.put("unknown", UNKNOWN);
+ ICON_NAME_TO_ICON.put("e", E);
+ ICON_NAME_TO_ICON.put("1x", ONE_X);
+ ICON_NAME_TO_ICON.put("g", G);
+ ICON_NAME_TO_ICON.put("h", H);
+ ICON_NAME_TO_ICON.put("h+", H_PLUS);
+ ICON_NAME_TO_ICON.put("4g", FOUR_G);
+ ICON_NAME_TO_ICON.put("4g+", FOUR_G_PLUS);
+ ICON_NAME_TO_ICON.put("lte", LTE);
+ ICON_NAME_TO_ICON.put("lte+", LTE_PLUS);
+ ICON_NAME_TO_ICON.put("5g", NR_5G);
+ ICON_NAME_TO_ICON.put("5g_plus", NR_5G_PLUS);
+ ICON_NAME_TO_ICON.put("datadisable", DATA_DISABLED);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/SensorPrivacyTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/SensorPrivacyTileTest.java
new file mode 100644
index 000000000000..90792e3d958f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/SensorPrivacyTileTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.SensorPrivacyManager;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class SensorPrivacyTileTest extends SysuiTestCase {
+
+ @Mock
+ private KeyguardMonitor mKeyguard;
+ @Mock
+ private QSTileHost mHost;
+ @Mock
+ SensorPrivacyManager mSensorPrivacyManager;
+
+ private TestableLooper mTestableLooper;
+
+ private SensorPrivacyTile mSensorPrivacyTile;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mTestableLooper = TestableLooper.get(this);
+ mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
+ mKeyguard = mDependency.injectMockDependency(KeyguardMonitor.class);
+
+ mSensorPrivacyManager = mDependency.injectMockDependency(SensorPrivacyManager.class);
+
+ when(mHost.getContext()).thenReturn(mContext);
+
+ mSensorPrivacyTile = new SensorPrivacyTile(mHost);
+ }
+
+ @Test
+ public void testSensorPrivacyListenerAdded_handleListeningTrue() {
+ // To prevent access to privacy related features from apps with WRITE_SECURE_SETTINGS the
+ // sensor privacy state is not stored in Settings; to receive notification apps must add
+ // themselves as a listener with the SensorPrivacyManager. This test verifies when
+ // setListening is called with a value of true the tile adds itself as a listener.
+ mSensorPrivacyTile.handleSetListening(true);
+ mTestableLooper.processAllMessages();
+ verify(mSensorPrivacyManager).addSensorPrivacyListener(mSensorPrivacyTile);
+ }
+
+ @Test
+ public void testSensorPrivacyListenerRemoved_handleListeningFalse() {
+ // Similar to the test above verifies that the tile removes itself as a listener when
+ // setListening is called with a value of false.
+ mSensorPrivacyTile.handleSetListening(false);
+ mTestableLooper.processAllMessages();
+ verify(mSensorPrivacyManager).removeSensorPrivacyListener((mSensorPrivacyTile));
+ }
+
+ @Test
+ public void testSensorPrivacyEnabled_handleClick() {
+ // Verifies when the SensorPrivacy tile is clicked it invokes the SensorPrivacyManager to
+ // set sensor privacy.
+ mSensorPrivacyTile.getState().value = false;
+ mSensorPrivacyTile.handleClick();
+ mTestableLooper.processAllMessages();
+ verify(mSensorPrivacyManager).setSensorPrivacy(true);
+
+ mSensorPrivacyTile.getState().value = true;
+ mSensorPrivacyTile.handleClick();
+ mTestableLooper.processAllMessages();
+ verify(mSensorPrivacyManager).setSensorPrivacy(false);
+ }
+
+ @Test
+ public void testSensorPrivacyNotDisabled_keyguard() {
+ // Verifies when the device is locked that sensor privacy cannot be disabled
+ when(mKeyguard.isSecure()).thenReturn(true);
+ when(mKeyguard.isShowing()).thenReturn(true);
+ mSensorPrivacyTile.getState().value = true;
+ mSensorPrivacyTile.handleClick();
+ mTestableLooper.processAllMessages();
+ verify(mSensorPrivacyManager, never()).setSensorPrivacy(false);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java
index 8e88ed0556bf..def7513bc7dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java
@@ -475,14 +475,14 @@ public class NotificationDataTest extends SysuiTestCase {
outRanking.getImportance(), outRanking.getImportanceExplanation(),
outRanking.getOverrideGroupKey(), outRanking.getChannel(), null, null,
outRanking.canShowBadge(), outRanking.getUserSentiment(), true,
- false, false, null, null);
+ -1, false, null, null);
} else if (key.equals(TEST_EXEMPT_DND_VISUAL_SUPPRESSION_KEY)) {
outRanking.populate(key, outRanking.getRank(),
outRanking.matchesInterruptionFilter(),
outRanking.getVisibilityOverride(), 255,
outRanking.getImportance(), outRanking.getImportanceExplanation(),
outRanking.getOverrideGroupKey(), outRanking.getChannel(), null, null,
- outRanking.canShowBadge(), outRanking.getUserSentiment(), true, false,
+ outRanking.canShowBadge(), outRanking.getUserSentiment(), true, -1,
false, null, null);
} else {
outRanking.populate(key, outRanking.getRank(),
@@ -490,7 +490,7 @@ public class NotificationDataTest extends SysuiTestCase {
outRanking.getVisibilityOverride(), outRanking.getSuppressedVisualEffects(),
outRanking.getImportance(), outRanking.getImportanceExplanation(),
outRanking.getOverrideGroupKey(), NOTIFICATION_CHANNEL, null, null,
- outRanking.canShowBadge(), outRanking.getUserSentiment(), false, false,
+ outRanking.canShowBadge(), outRanking.getUserSentiment(), false, -1,
false, null, null);
}
return true;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index 8706e214db8e..904e5b99b0ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -167,7 +167,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
0,
NotificationManager.IMPORTANCE_DEFAULT,
null, null,
- null, null, null, true, sentiment, false, false, false, null, null);
+ null, null, null, true, sentiment, false, -1, false, null, null);
return true;
}).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class));
}
@@ -185,7 +185,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
NotificationManager.IMPORTANCE_DEFAULT,
null, null,
null, null, null, true,
- NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL, false, false,
+ NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL, false, -1,
false, smartActions, null);
return true;
}).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
index c3bc5110febe..ee39e10b4165 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
@@ -32,14 +32,19 @@ import android.testing.TestableLooper;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.AmbientPulseManager;
+import com.android.systemui.statusbar.notification.AlertTransferListener;
import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationData.Entry;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -49,13 +54,15 @@ import java.util.HashMap;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase {
- @Rule
- public MockitoRule rule = MockitoJUnit.rule();
+ @Rule public MockitoRule rule = MockitoJUnit.rule();
private NotificationGroupAlertTransferHelper mGroupAlertTransferHelper;
private NotificationGroupManager mGroupManager;
private AmbientPulseManager mAmbientPulseManager;
private HeadsUpManager mHeadsUpManager;
+ @Mock private NotificationEntryManager mNotificationEntryManager;
+ @Captor private ArgumentCaptor<AlertTransferListener> mListenerCaptor;
+ private AlertTransferListener mAlertTransferListener;
private final HashMap<String, Entry> mPendingEntries = new HashMap<>();
private final NotificationGroupTestHelper mGroupTestHelper =
new NotificationGroupTestHelper(mContext);
@@ -67,15 +74,19 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase {
mDependency.injectTestDependency(AmbientPulseManager.class, mAmbientPulseManager);
mHeadsUpManager = new HeadsUpManager(mContext) {};
+ when(mNotificationEntryManager.getPendingNotificationsIterator())
+ .thenReturn(mPendingEntries.values());
+
mGroupManager = new NotificationGroupManager();
mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager);
mGroupManager.setHeadsUpManager(mHeadsUpManager);
mGroupAlertTransferHelper = new NotificationGroupAlertTransferHelper();
mGroupAlertTransferHelper.setHeadsUpManager(mHeadsUpManager);
- mGroupAlertTransferHelper.setPendingEntries(mPendingEntries);
- mGroupManager.addOnGroupChangeListener(mGroupAlertTransferHelper);
+ mGroupAlertTransferHelper.bind(mNotificationEntryManager, mGroupManager);
+ verify(mNotificationEntryManager).setAlertTransferListener(mListenerCaptor.capture());
+ mAlertTransferListener = mListenerCaptor.getValue();
mHeadsUpManager.addListener(mGroupAlertTransferHelper);
mAmbientPulseManager.addListener(mGroupAlertTransferHelper);
}
@@ -110,7 +121,7 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase {
// Add second child notification so that summary is no longer suppressed.
mPendingEntries.put(childEntry2.key, childEntry2);
- mGroupAlertTransferHelper.onPendingEntryAdded(childEntry2);
+ mAlertTransferListener.onPendingEntryAdded(childEntry2);
mGroupManager.onEntryAdded(childEntry2);
// The alert state should transfer back to the summary as there is now more than one
@@ -137,7 +148,7 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase {
// Add second child notification so that summary is no longer suppressed.
mPendingEntries.put(childEntry2.key, childEntry2);
- mGroupAlertTransferHelper.onPendingEntryAdded(childEntry2);
+ mAlertTransferListener.onPendingEntryAdded(childEntry2);
mGroupManager.onEntryAdded(childEntry2);
// Dozing changed so no reason to re-alert summary.
@@ -175,7 +186,7 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase {
when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag()))
.thenReturn(true);
- mGroupAlertTransferHelper.onInflationFinished(childEntry);
+ mAlertTransferListener.onEntryReinflated(childEntry);
// Alert is immediately removed from summary, and we show child as its content is inflated.
assertFalse(mHeadsUpManager.isAlerting(summaryEntry.key));
@@ -199,13 +210,13 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase {
// Add second child notification so that summary is no longer suppressed.
mPendingEntries.put(childEntry2.key, childEntry2);
- mGroupAlertTransferHelper.onPendingEntryAdded(childEntry2);
+ mAlertTransferListener.onPendingEntryAdded(childEntry2);
mGroupManager.onEntryAdded(childEntry2);
// Child entry finishes its inflation.
when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag()))
.thenReturn(true);
- mGroupAlertTransferHelper.onInflationFinished(childEntry);
+ mAlertTransferListener.onEntryReinflated(childEntry);
verify(childEntry.getRow(), times(1)).freeContentViewWhenSafe(mHeadsUpManager
.getContentFlag());
@@ -225,7 +236,7 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase {
mGroupManager.onEntryAdded(summaryEntry);
mGroupManager.onEntryAdded(childEntry);
- mGroupAlertTransferHelper.cleanUpPendingAlertInfo(childEntry.key);
+ mAlertTransferListener.onEntryRemoved(childEntry);
assertFalse(mGroupAlertTransferHelper.isAlertTransferPending(childEntry));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index 35f0dba12cb9..fdbf09083a4e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -212,6 +212,11 @@ public class NetworkControllerBaseTest extends SysuiTestCase {
NetworkCapabilities.TRANSPORT_CELLULAR, true, true);
}
+ public void setupDefaultNr5GIconConfiguration() {
+ NetworkControllerImpl.Config.add5GIconMapping("connected_mmwave:5g_plus", mConfig);
+ NetworkControllerImpl.Config.add5GIconMapping("connected:5g", mConfig);
+ }
+
public void setConnectivityViaBroadcast(
int networkType, boolean validated, boolean isConnected) {
setConnectivityCommon(networkType, validated, isConnected);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index d42940a3d2a2..2baea1ae3b19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -1,11 +1,14 @@
package com.android.systemui.statusbar.policy;
import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.net.NetworkCapabilities;
import android.os.Looper;
+import android.telephony.NetworkRegistrationState;
+import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
@@ -16,6 +19,7 @@ import com.android.settingslib.net.DataUsageController;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -141,6 +145,47 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest {
}
@Test
+ public void testNr5GIcon_NrConnectedWithoutMMWave_show5GIcon() {
+ setupDefaultNr5GIconConfiguration();
+ setupDefaultSignal();
+ updateDataConnectionState(TelephonyManager.DATA_CONNECTED,
+ TelephonyManager.NETWORK_TYPE_LTE);
+ ServiceState ss = Mockito.mock(ServiceState.class);
+ doReturn(NetworkRegistrationState.NR_STATUS_CONNECTED).when(ss).getNrStatus();
+ doReturn(ServiceState.FREQUENCY_RANGE_HIGH).when(ss).getNrFrequencyRange();
+ mPhoneStateListener.onServiceStateChanged(ss);
+
+ verifyDataIndicators(TelephonyIcons.ICON_5G);
+ }
+
+ @Test
+ public void testNr5GIcon_NrConnectedWithMMWave_show5GPlusIcon() {
+ setupDefaultNr5GIconConfiguration();
+ setupDefaultSignal();
+ updateDataConnectionState(TelephonyManager.DATA_CONNECTED,
+ TelephonyManager.NETWORK_TYPE_LTE);
+ ServiceState ss = Mockito.mock(ServiceState.class);
+ doReturn(NetworkRegistrationState.NR_STATUS_CONNECTED).when(ss).getNrStatus();
+ doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(ss).getNrFrequencyRange();
+ mPhoneStateListener.onServiceStateChanged(ss);
+
+ verifyDataIndicators(TelephonyIcons.ICON_5G_PLUS);
+ }
+
+ @Test
+ public void testNr5GIcon_NrRestricted_showLteIcon() {
+ setupDefaultNr5GIconConfiguration();
+ setupDefaultSignal();
+ updateDataConnectionState(TelephonyManager.DATA_CONNECTED,
+ TelephonyManager.NETWORK_TYPE_LTE);
+ ServiceState ss = Mockito.mock(ServiceState.class);
+ doReturn(NetworkRegistrationState.NR_STATUS_RESTRICTED).when(ss).getNrStatus();
+ mPhoneStateListener.onServiceStateChanged(mServiceState);
+
+ verifyDataIndicators(TelephonyIcons.ICON_LTE);
+ }
+
+ @Test
public void testDataDisabledIcon_UserNotSetup() {
setupNetworkController();
when(mMockTm.getDataEnabled(mSubId)).thenReturn(false);
@@ -222,5 +267,4 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest {
true, DEFAULT_QS_SIGNAL_STRENGTH, dataIcon, false,
false);
}
-
}
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index eb0090be4747..66d64b1a5f7a 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -6642,6 +6642,13 @@ message MetricsEvent {
// OS: Q
SETTINGS_FINANCIAL_APPS_SMS_ACCESS = 1597;
+ // OPEN: QS Sensor Privacy Mode tile shown
+ // ACTION: QS Sensor Privacy Mode tile tapped
+ // SUBTYPE: 0 is off, 1 is on
+ // CATEGORY: QUICK_SETTINGS
+ // OS: Q
+ QS_SENSOR_PRIVACY = 1598;
+
// ---- End Q Constants, all Q constants go above this line ----
// Add new aosp constants above this line.
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index a917ced2ae68..2cbab492a605 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -477,6 +477,7 @@ public class BackupManagerService {
* requires on-screen confirmation by the user.
*/
public void adbBackup(
+ @UserIdInt int userId,
ParcelFileDescriptor fd,
boolean includeApks,
boolean includeObbs,
@@ -487,6 +488,8 @@ public class BackupManagerService {
boolean doCompress,
boolean doKeyValue,
String[] packageNames) {
+ enforceCallingPermissionOnUserId(userId, "adbBackup");
+
mUserBackupManagerService.adbBackup(
fd,
includeApks,
@@ -505,7 +508,9 @@ public class BackupManagerService {
* is synchronous and does not return to the caller until the restore has been completed. It
* requires on-screen confirmation by the user.
*/
- public void adbRestore(ParcelFileDescriptor fd) {
+ public void adbRestore(@UserIdInt int userId, ParcelFileDescriptor fd) {
+ enforceCallingPermissionOnUserId(userId, "setBackupEnabled");
+
mUserBackupManagerService.adbRestore(fd);
}
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index ed6ff9b1d77d..4acd5c494ac7 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -389,14 +389,13 @@ public class Trampoline extends IBackupManager.Stub {
backupNowForUser(binderGetCallingUserId());
}
- @Override
- public void adbBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
- boolean includeShared, boolean doWidgets, boolean allApps,
- boolean allIncludesSystem, boolean doCompress, boolean doKeyValue, String[] packageNames)
- throws RemoteException {
+ public void adbBackup(@UserIdInt int userId, ParcelFileDescriptor fd,
+ boolean includeApks, boolean includeObbs, boolean includeShared, boolean doWidgets,
+ boolean allApps, boolean allIncludesSystem, boolean doCompress, boolean doKeyValue,
+ String[] packageNames) throws RemoteException {
BackupManagerService svc = mService;
if (svc != null) {
- svc.adbBackup(fd, includeApks, includeObbs, includeShared, doWidgets,
+ svc.adbBackup(userId, fd, includeApks, includeObbs, includeShared, doWidgets,
allApps, allIncludesSystem, doCompress, doKeyValue, packageNames);
}
}
@@ -410,10 +409,10 @@ public class Trampoline extends IBackupManager.Stub {
}
@Override
- public void adbRestore(ParcelFileDescriptor fd) throws RemoteException {
+ public void adbRestore(@UserIdInt int userId, ParcelFileDescriptor fd) throws RemoteException {
BackupManagerService svc = mService;
if (svc != null) {
- svc.adbRestore(fd);
+ svc.adbRestore(userId, fd);
}
}
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 5220a590ddda..796ef406d333 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -37,6 +37,7 @@ import static com.android.server.backup.internal.BackupHandler.MSG_RUN_RESTORE;
import static com.android.server.backup.internal.BackupHandler.MSG_SCHEDULE_BACKUP_PACKAGE;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.AppGlobals;
@@ -2423,9 +2424,9 @@ public class UserBackupManagerService {
* return to the caller until the backup has been completed. It requires on-screen confirmation
* by the user.
*/
- public void adbBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
- boolean includeShared, boolean doWidgets, boolean doAllApps, boolean includeSystem,
- boolean compress, boolean doKeyValue, String[] pkgList) {
+ public void adbBackup(ParcelFileDescriptor fd, boolean includeApks,
+ boolean includeObbs, boolean includeShared, boolean doWidgets, boolean doAllApps,
+ boolean includeSystem, boolean compress, boolean doKeyValue, String[] pkgList) {
mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "adbBackup");
final int callingUserHandle = UserHandle.getCallingUserId();
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 872fe4229479..10e713d9dabe 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -33,6 +33,7 @@ import android.os.ShellCallback;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
+import android.view.contentcapture.ContentCaptureContext;
import android.view.contentcapture.ContentCaptureEvent;
import android.view.contentcapture.IContentCaptureManager;
@@ -165,7 +166,8 @@ public final class ContentCaptureManagerService extends
@Override
public void startSession(@UserIdInt int userId, @NonNull IBinder activityToken,
@NonNull ComponentName componentName, @NonNull String sessionId,
- int flags, @NonNull IResultReceiver result) {
+ @Nullable ContentCaptureContext clientContext, int flags,
+ @NonNull IResultReceiver result) {
Preconditions.checkNotNull(activityToken);
Preconditions.checkNotNull(componentName);
Preconditions.checkNotNull(sessionId);
@@ -180,7 +182,7 @@ public final class ContentCaptureManagerService extends
synchronized (mLock) {
final ContentCapturePerUserService service = getServiceForUserLocked(userId);
service.startSessionLocked(activityToken, componentName, taskId, displayId,
- sessionId, flags, mAllowInstantService, result);
+ sessionId, clientContext, flags, mAllowInstantService, result);
}
}
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
index aa171f4a0818..81309122d0ca 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
@@ -37,8 +37,9 @@ import android.os.RemoteException;
import android.service.contentcapture.SnapshotData;
import android.util.ArrayMap;
import android.util.Slog;
+import android.view.contentcapture.ContentCaptureContext;
import android.view.contentcapture.ContentCaptureEvent;
-import android.view.contentcapture.ContentCaptureManager;
+import android.view.contentcapture.ContentCaptureSession;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.IResultReceiver;
@@ -59,7 +60,7 @@ final class ContentCapturePerUserService
private static final String TAG = ContentCaptureManagerService.class.getSimpleName();
@GuardedBy("mLock")
- private final ArrayMap<String, ContentCaptureSession> mSessions =
+ private final ArrayMap<String, ContentCaptureServerSession> mSessions =
new ArrayMap<>();
// TODO(b/111276913): add mechanism to prune stale sessions, similar to Autofill's
@@ -113,10 +114,10 @@ final class ContentCapturePerUserService
@GuardedBy("mLock")
public void startSessionLocked(@NonNull IBinder activityToken,
@NonNull ComponentName componentName, int taskId, int displayId,
- @NonNull String sessionId, int flags, boolean bindInstantServiceAllowed,
- @NonNull IResultReceiver resultReceiver) {
+ @NonNull String sessionId, @Nullable ContentCaptureContext clientContext,
+ int flags, boolean bindInstantServiceAllowed, @NonNull IResultReceiver resultReceiver) {
if (!isEnabledLocked()) {
- sendToClient(resultReceiver, ContentCaptureManager.STATE_DISABLED);
+ sendToClient(resultReceiver, ContentCaptureSession.STATE_DISABLED);
return;
}
final ComponentName serviceComponentName = getServiceComponentName();
@@ -130,7 +131,7 @@ final class ContentCapturePerUserService
return;
}
- ContentCaptureSession session = mSessions.get(sessionId);
+ ContentCaptureServerSession session = mSessions.get(sessionId);
if (session != null) {
if (mMaster.debug) {
Slog.d(TAG, "startSession(): reusing session " + sessionId + " for "
@@ -139,20 +140,20 @@ final class ContentCapturePerUserService
// TODO(b/111276913): check if local ids match and decide what to do if they don't
// TODO(b/111276913): should we call session.notifySessionStartedLocked() again??
// if not, move notifySessionStartedLocked() into session constructor
- sendToClient(resultReceiver, ContentCaptureManager.STATE_ACTIVE);
+ sendToClient(resultReceiver, ContentCaptureSession.STATE_ACTIVE);
return;
}
- session = new ContentCaptureSession(getContext(), mUserId, mLock, activityToken,
- this, serviceComponentName, componentName, taskId, displayId, sessionId, flags,
- bindInstantServiceAllowed, mMaster.verbose);
+ session = new ContentCaptureServerSession(getContext(), mUserId, mLock, activityToken,
+ this, serviceComponentName, componentName, taskId, displayId, sessionId,
+ clientContext, flags, bindInstantServiceAllowed, mMaster.verbose);
if (mMaster.verbose) {
Slog.v(TAG, "startSession(): new session for " + componentName + " and id "
+ sessionId);
}
mSessions.put(sessionId, session);
session.notifySessionStartedLocked();
- sendToClient(resultReceiver, ContentCaptureManager.STATE_ACTIVE);
+ sendToClient(resultReceiver, ContentCaptureSession.STATE_ACTIVE);
}
// TODO(b/111276913): log metrics
@@ -163,7 +164,7 @@ final class ContentCapturePerUserService
return;
}
- final ContentCaptureSession session = mSessions.get(sessionId);
+ final ContentCaptureServerSession session = mSessions.get(sessionId);
if (session == null) {
if (mMaster.debug) {
Slog.d(TAG, "finishSession(): no session with id" + sessionId);
@@ -194,7 +195,7 @@ final class ContentCapturePerUserService
if (!isEnabledLocked()) {
return;
}
- final ContentCaptureSession session = mSessions.get(sessionId);
+ final ContentCaptureServerSession session = mSessions.get(sessionId);
if (session == null) {
if (mMaster.verbose) {
Slog.v(TAG, "sendEvents(): no session for " + sessionId);
@@ -212,7 +213,7 @@ final class ContentCapturePerUserService
@NonNull Bundle data) {
final String id = getSessionId(activityToken);
if (id != null) {
- final ContentCaptureSession session = mSessions.get(id);
+ final ContentCaptureServerSession session = mSessions.get(id);
final Bundle assistData = data.getBundle(ASSIST_KEY_DATA);
final AssistStructure assistStructure = data.getParcelable(ASSIST_KEY_STRUCTURE);
final AssistContent assistContent = data.getParcelable(ASSIST_KEY_CONTENT);
@@ -237,9 +238,9 @@ final class ContentCapturePerUserService
}
@GuardedBy("mLock")
- private ContentCaptureSession getSession(@NonNull IBinder activityToken) {
+ private ContentCaptureServerSession getSession(@NonNull IBinder activityToken) {
for (int i = 0; i < mSessions.size(); i++) {
- final ContentCaptureSession session = mSessions.valueAt(i);
+ final ContentCaptureServerSession session = mSessions.valueAt(i);
if (session.mActivityToken.equals(activityToken)) {
return session;
}
@@ -262,7 +263,7 @@ final class ContentCapturePerUserService
void destroySessionsLocked() {
final int numSessions = mSessions.size();
for (int i = 0; i < numSessions; i++) {
- final ContentCaptureSession session = mSessions.valueAt(i);
+ final ContentCaptureServerSession session = mSessions.valueAt(i);
session.destroyLocked(true);
}
mSessions.clear();
@@ -272,7 +273,7 @@ final class ContentCapturePerUserService
void listSessionsLocked(ArrayList<String> output) {
final int numSessions = mSessions.size();
for (int i = 0; i < numSessions; i++) {
- final ContentCaptureSession session = mSessions.valueAt(i);
+ final ContentCaptureServerSession session = mSessions.valueAt(i);
output.add(session.toShortString());
}
}
@@ -288,7 +289,7 @@ final class ContentCapturePerUserService
final String prefix2 = prefix + " ";
for (int i = 0; i < size; i++) {
pw.print(prefix); pw.print("session@"); pw.println(i);
- final ContentCaptureSession session = mSessions.valueAt(i);
+ final ContentCaptureServerSession session = mSessions.valueAt(i);
session.dumpLocked(prefix2, pw);
}
}
@@ -300,7 +301,7 @@ final class ContentCapturePerUserService
@GuardedBy("mLock")
private String getSessionId(@NonNull IBinder activityToken) {
for (int i = 0; i < mSessions.size(); i++) {
- ContentCaptureSession session = mSessions.valueAt(i);
+ ContentCaptureServerSession session = mSessions.valueAt(i);
if (session.isActivitySession(activityToken)) {
return mSessions.keyAt(i);
}
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureSession.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java
index a4012d5faa8a..181a2daa2467 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureSession.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java
@@ -16,15 +16,16 @@
package com.android.server.contentcapture;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.os.IBinder;
import android.service.contentcapture.ContentCaptureService;
-import android.service.contentcapture.InteractionContext;
-import android.service.contentcapture.InteractionSessionId;
import android.service.contentcapture.SnapshotData;
import android.util.Slog;
+import android.view.contentcapture.ContentCaptureContext;
import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.ContentCaptureSessionId;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
@@ -33,21 +34,22 @@ import com.android.server.contentcapture.RemoteContentCaptureService.ContentCapt
import java.io.PrintWriter;
import java.util.List;
-final class ContentCaptureSession implements ContentCaptureServiceCallbacks {
+final class ContentCaptureServerSession implements ContentCaptureServiceCallbacks {
- private static final String TAG = "ContentCaptureSession";
+ private static final String TAG = ContentCaptureServerSession.class.getSimpleName();
private final Object mLock;
final IBinder mActivityToken;
private final ContentCapturePerUserService mService;
private final RemoteContentCaptureService mRemoteService;
- private final InteractionContext mInterationContext;
+ private final ContentCaptureContext mContentCaptureContext;
private final String mId;
- ContentCaptureSession(@NonNull Context context, int userId, @NonNull Object lock,
+ ContentCaptureServerSession(@NonNull Context context, int userId, @NonNull Object lock,
@NonNull IBinder activityToken, @NonNull ContentCapturePerUserService service,
@NonNull ComponentName serviceComponentName, @NonNull ComponentName appComponentName,
- int taskId, int displayId, @NonNull String sessionId, int flags,
+ int taskId, int displayId, @NonNull String sessionId,
+ @Nullable ContentCaptureContext clientContext, int flags,
boolean bindInstantServiceAllowed, boolean verbose) {
mLock = lock;
mActivityToken = activityToken;
@@ -56,7 +58,8 @@ final class ContentCaptureSession implements ContentCaptureServiceCallbacks {
mRemoteService = new RemoteContentCaptureService(context,
ContentCaptureService.SERVICE_INTERFACE, serviceComponentName, userId, this,
bindInstantServiceAllowed, verbose);
- mInterationContext = new InteractionContext(appComponentName, taskId, displayId, flags);
+ mContentCaptureContext = new ContentCaptureContext(clientContext, appComponentName, taskId,
+ displayId, flags);
}
/**
@@ -71,7 +74,7 @@ final class ContentCaptureSession implements ContentCaptureServiceCallbacks {
*/
@GuardedBy("mLock")
public void notifySessionStartedLocked() {
- mRemoteService.onSessionLifecycleRequest(mInterationContext, mId);
+ mRemoteService.onSessionLifecycleRequest(mContentCaptureContext, mId);
}
/**
@@ -93,7 +96,7 @@ final class ContentCaptureSession implements ContentCaptureServiceCallbacks {
* Cleans up the session and removes it from the service.
*
* @param notifyRemoteService whether it should trigger a {@link
- * ContentCaptureService#onDestroyInteractionSession(InteractionSessionId)}
+ * ContentCaptureService#onDestroyContentCaptureSession(ContentCaptureSessionId)}
* request.
*/
@GuardedBy("mLock")
@@ -109,7 +112,7 @@ final class ContentCaptureSession implements ContentCaptureServiceCallbacks {
* Cleans up the session, but not removes it from the service.
*
* @param notifyRemoteService whether it should trigger a {@link
- * ContentCaptureService#onDestroyInteractionSession(InteractionSessionId)}
+ * ContentCaptureService#onDestroyContentCaptureSession(ContentCaptureSessionId)}
* request.
*/
@GuardedBy("mLock")
@@ -137,7 +140,7 @@ final class ContentCaptureSession implements ContentCaptureServiceCallbacks {
@GuardedBy("mLock")
public void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) {
pw.print(prefix); pw.print("id: "); pw.print(mId); pw.println();
- pw.print(prefix); pw.print("context: "); mInterationContext.dump(pw); pw.println();
+ pw.print(prefix); pw.print("context: "); mContentCaptureContext.dump(pw); pw.println();
pw.print(prefix); pw.print("activity token: "); pw.println(mActivityToken);
pw.print(prefix); pw.print("has autofill callback: ");
}
diff --git a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
index 33b6c8d5eec4..b4edf7e60bc4 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
@@ -22,9 +22,9 @@ import android.content.Context;
import android.os.IBinder;
import android.service.contentcapture.ContentCaptureEventsRequest;
import android.service.contentcapture.IContentCaptureService;
-import android.service.contentcapture.InteractionContext;
import android.service.contentcapture.SnapshotData;
import android.text.format.DateUtils;
+import android.view.contentcapture.ContentCaptureContext;
import android.view.contentcapture.ContentCaptureEvent;
import com.android.server.infra.AbstractMultiplePendingRequestsRemoteService;
@@ -66,17 +66,17 @@ final class RemoteContentCaptureService
}
/**
- * Called by {@link ContentCaptureSession} to generate a call to the
+ * Called by {@link ContentCaptureServerSession} to generate a call to the
* {@link RemoteContentCaptureService} to indicate the session was created (when {@code context}
* is not {@code null} or destroyed (when {@code context} is {@code null}).
*/
- public void onSessionLifecycleRequest(@Nullable InteractionContext context,
+ public void onSessionLifecycleRequest(@Nullable ContentCaptureContext context,
@NonNull String sessionId) {
scheduleAsyncRequest((s) -> s.onSessionLifecycle(context, sessionId));
}
/**
- * Called by {@link ContentCaptureSession} to send a batch of events to the service.
+ * Called by {@link ContentCaptureServerSession} to send a batch of events to the service.
*/
public void onContentCaptureEventsRequest(@NonNull String sessionId,
@NonNull List<ContentCaptureEvent> events) {
@@ -85,7 +85,7 @@ final class RemoteContentCaptureService
}
/**
- * Called by {@link ContentCaptureSession} to send snapshot data to the service.
+ * Called by {@link ContentCaptureServerSession} to send snapshot data to the service.
*/
public void onActivitySnapshotRequest(@NonNull String sessionId,
@NonNull SnapshotData snapshotData) {
@@ -94,8 +94,8 @@ final class RemoteContentCaptureService
public interface ContentCaptureServiceCallbacks
extends VultureCallback<RemoteContentCaptureService> {
- // NOTE: so far we don't need to notify the callback implementation (an inner class on
- // AutofillManagerServiceImpl) of the request results (success, timeouts, etc..), so this
+ // NOTE: so far we don't need to notify the callback implementation
+ // (ContentCaptureServerSession) of the request results (success, timeouts, etc..), so this
// callback interface is empty.
}
}
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 8b992ebcf059..4e8ef54a6465 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -22,6 +22,7 @@ import static android.provider.Settings.Global.LOCATION_DISABLE_STATUS_CALLBACKS
import static com.android.internal.util.Preconditions.checkState;
+import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -189,6 +190,8 @@ public class LocationManagerService extends ILocationManager.Stub {
private LocationBlacklist mBlacklist;
private GnssMeasurementsProvider mGnssMeasurementsProvider;
private GnssNavigationMessageProvider mGnssNavigationMessageProvider;
+ private String mLocationControllerExtraPackage;
+ private boolean mLocationControllerExtraPackageEnabled;
private IGpsGeofenceHardware mGpsGeofenceProxy;
// --- fields below are protected by mLock ---
@@ -2717,6 +2720,39 @@ public class LocationManagerService extends ILocationManager.Stub {
return null;
}
+ @Override
+ public void setLocationControllerExtraPackage(String packageName) {
+ mContext.enforceCallingPermission(Manifest.permission.LOCATION_HARDWARE,
+ Manifest.permission.LOCATION_HARDWARE + " permission required");
+ synchronized (mLock) {
+ mLocationControllerExtraPackage = packageName;
+ }
+ }
+
+ @Override
+ public String getLocationControllerExtraPackage() {
+ synchronized (mLock) {
+ return mLocationControllerExtraPackage;
+ }
+ }
+
+ @Override
+ public void setLocationControllerExtraPackageEnabled(boolean enabled) {
+ mContext.enforceCallingPermission(Manifest.permission.LOCATION_HARDWARE,
+ Manifest.permission.LOCATION_HARDWARE + " permission required");
+ synchronized (mLock) {
+ mLocationControllerExtraPackageEnabled = enabled;
+ }
+ }
+
+ @Override
+ public boolean isLocationControllerExtraPackageEnabled() {
+ synchronized (mLock) {
+ return mLocationControllerExtraPackageEnabled
+ && (mLocationControllerExtraPackage != null);
+ }
+ }
+
/**
* Returns the current location enabled/disabled status for a user
*
@@ -2763,7 +2799,7 @@ public class LocationManagerService extends ILocationManager.Stub {
*/
@Override
public void setLocationEnabledForUser(boolean enabled, int userId) {
- mContext.enforceCallingPermission(
+ mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.WRITE_SECURE_SETTINGS,
"Requires WRITE_SECURE_SETTINGS permission");
@@ -3492,6 +3528,11 @@ public class LocationManagerService extends ILocationManager.Stub {
}
}
+ if (mLocationControllerExtraPackage != null) {
+ pw.println(" Location controller extra package: " + mLocationControllerExtraPackage
+ + " enabled: " + mLocationControllerExtraPackageEnabled);
+ }
+
if (!mBackgroundThrottlePackageWhitelist.isEmpty()) {
pw.println(" Throttling Whitelisted Packages:");
for (String packageName : mBackgroundThrottlePackageWhitelist) {
diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java
new file mode 100644
index 000000000000..1cbcbe5a8bdb
--- /dev/null
+++ b/services/core/java/com/android/server/SensorPrivacyService.java
@@ -0,0 +1,426 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.hardware.ISensorPrivacyListener;
+import android.hardware.ISensorPrivacyManager;
+import android.location.LocationManager;
+import android.net.ConnectivityManager;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+import com.android.internal.util.function.pooled.PooledLambda;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.NoSuchElementException;
+
+/** @hide */
+public final class SensorPrivacyService extends SystemService {
+
+ private static final String TAG = "SensorPrivacyService";
+
+ private static final String SENSOR_PRIVACY_XML_FILE = "sensor_privacy.xml";
+ private static final String XML_TAG_SENSOR_PRIVACY = "sensor-privacy";
+ private static final String XML_ATTRIBUTE_ENABLED = "enabled";
+
+ private final SensorPrivacyServiceImpl mSensorPrivacyServiceImpl;
+
+ public SensorPrivacyService(Context context) {
+ super(context);
+ mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl(context);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.SENSOR_PRIVACY_SERVICE, mSensorPrivacyServiceImpl);
+ }
+
+ class SensorPrivacyServiceImpl extends ISensorPrivacyManager.Stub {
+
+ private final SensorPrivacyHandler mHandler;
+ private final Context mContext;
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private final AtomicFile mAtomicFile;
+ @GuardedBy("mLock")
+ private boolean mEnabled;
+
+ SensorPrivacyServiceImpl(Context context) {
+ mContext = context;
+ mHandler = new SensorPrivacyHandler(FgThread.get().getLooper(), mContext);
+ File sensorPrivacyFile = new File(Environment.getDataSystemDirectory(),
+ SENSOR_PRIVACY_XML_FILE);
+ mAtomicFile = new AtomicFile(sensorPrivacyFile);
+ synchronized (mLock) {
+ mEnabled = readPersistedSensorPrivacyEnabledLocked();
+ }
+ }
+
+ /**
+ * Sets the sensor privacy to the provided state and notifies all listeners of the new
+ * state.
+ */
+ @Override
+ public void setSensorPrivacy(boolean enable) {
+ enforceSensorPrivacyPermission();
+ synchronized (mLock) {
+ mEnabled = enable;
+ FileOutputStream outputStream = null;
+ try {
+ XmlSerializer serializer = new FastXmlSerializer();
+ outputStream = mAtomicFile.startWrite();
+ serializer.setOutput(outputStream, StandardCharsets.UTF_8.name());
+ serializer.startDocument(null, true);
+ serializer.startTag(null, XML_TAG_SENSOR_PRIVACY);
+ serializer.attribute(null, XML_ATTRIBUTE_ENABLED, String.valueOf(enable));
+ serializer.endTag(null, XML_TAG_SENSOR_PRIVACY);
+ serializer.endDocument();
+ mAtomicFile.finishWrite(outputStream);
+ } catch (IOException e) {
+ Log.e(TAG, "Caught an exception persisting the sensor privacy state: ", e);
+ mAtomicFile.failWrite(outputStream);
+ }
+ }
+ mHandler.onSensorPrivacyChanged(enable);
+ }
+
+ /**
+ * Enforces the caller contains the necessary permission to change the state of sensor
+ * privacy.
+ */
+ private void enforceSensorPrivacyPermission() {
+ if (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.MANAGE_SENSOR_PRIVACY) == PERMISSION_GRANTED) {
+ return;
+ }
+ throw new SecurityException(
+ "Changing sensor privacy requires the following permission: "
+ + android.Manifest.permission.MANAGE_SENSOR_PRIVACY);
+ }
+
+ /**
+ * Returns whether sensor privacy is enabled.
+ */
+ @Override
+ public boolean isSensorPrivacyEnabled() {
+ synchronized (mLock) {
+ return mEnabled;
+ }
+ }
+
+ /**
+ * Returns the state of sensor privacy from persistent storage.
+ */
+ private boolean readPersistedSensorPrivacyEnabledLocked() {
+ // if the file does not exist then sensor privacy has not yet been enabled on
+ // the device.
+ if (!mAtomicFile.exists()) {
+ return false;
+ }
+ boolean enabled;
+ try (FileInputStream inputStream = mAtomicFile.openRead()) {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(inputStream, StandardCharsets.UTF_8.name());
+ XmlUtils.beginDocument(parser, XML_TAG_SENSOR_PRIVACY);
+ parser.next();
+ String tagName = parser.getName();
+ enabled = Boolean.valueOf(parser.getAttributeValue(null, XML_ATTRIBUTE_ENABLED));
+ } catch (IOException | XmlPullParserException e) {
+ Log.e(TAG, "Caught an exception reading the state from storage: ", e);
+ // Delete the file to prevent the same error on subsequent calls and assume sensor
+ // privacy is not enabled.
+ mAtomicFile.delete();
+ enabled = false;
+ }
+ return enabled;
+ }
+
+ /**
+ * Persists the state of sensor privacy.
+ */
+ private void persistSensorPrivacyState() {
+ synchronized (mLock) {
+ FileOutputStream outputStream = null;
+ try {
+ XmlSerializer serializer = new FastXmlSerializer();
+ outputStream = mAtomicFile.startWrite();
+ serializer.setOutput(outputStream, StandardCharsets.UTF_8.name());
+ serializer.startDocument(null, true);
+ serializer.startTag(null, XML_TAG_SENSOR_PRIVACY);
+ serializer.attribute(null, XML_ATTRIBUTE_ENABLED, String.valueOf(mEnabled));
+ serializer.endTag(null, XML_TAG_SENSOR_PRIVACY);
+ serializer.endDocument();
+ mAtomicFile.finishWrite(outputStream);
+ } catch (IOException e) {
+ Log.e(TAG, "Caught an exception persisting the sensor privacy state: ", e);
+ mAtomicFile.failWrite(outputStream);
+ }
+ }
+ }
+
+ /**
+ * Registers a listener to be notified when the sensor privacy state changes.
+ */
+ @Override
+ public void addSensorPrivacyListener(ISensorPrivacyListener listener) {
+ if (listener == null) {
+ throw new NullPointerException("listener cannot be null");
+ }
+ mHandler.addListener(listener);
+ }
+
+ /**
+ * Unregisters a listener from sensor privacy state change notifications.
+ */
+ @Override
+ public void removeSensorPrivacyListener(ISensorPrivacyListener listener) {
+ if (listener == null) {
+ throw new NullPointerException("listener cannot be null");
+ }
+ mHandler.removeListener(listener);
+ }
+ }
+
+ /**
+ * Handles sensor privacy state changes and notifying listeners of the change.
+ */
+ private final class SensorPrivacyHandler extends Handler {
+ private static final int MESSAGE_SENSOR_PRIVACY_CHANGED = 1;
+
+ private final Object mListenerLock = new Object();
+
+ @GuardedBy("mListenerLock")
+ private final RemoteCallbackList<ISensorPrivacyListener> mListeners =
+ new RemoteCallbackList<>();
+ private final ArrayMap<ISensorPrivacyListener, DeathRecipient> mDeathRecipients;
+ private final Context mContext;
+
+ SensorPrivacyHandler(Looper looper, Context context) {
+ super(looper);
+ mDeathRecipients = new ArrayMap<>();
+ mContext = context;
+ }
+
+ public void onSensorPrivacyChanged(boolean enabled) {
+ sendMessage(PooledLambda.obtainMessage(SensorPrivacyHandler::handleSensorPrivacyChanged,
+ this, enabled));
+ sendMessage(
+ PooledLambda.obtainMessage(SensorPrivacyServiceImpl::persistSensorPrivacyState,
+ mSensorPrivacyServiceImpl));
+ }
+
+ public void addListener(ISensorPrivacyListener listener) {
+ synchronized (mListenerLock) {
+ DeathRecipient deathRecipient = new DeathRecipient(listener);
+ mDeathRecipients.put(listener, deathRecipient);
+ mListeners.register(listener);
+ }
+ }
+
+ public void removeListener(ISensorPrivacyListener listener) {
+ synchronized (mListenerLock) {
+ DeathRecipient deathRecipient = mDeathRecipients.remove(listener);
+ if (deathRecipient != null) {
+ deathRecipient.destroy();
+ }
+ mListeners.unregister(listener);
+ }
+ }
+
+ public void handleSensorPrivacyChanged(boolean enabled) {
+ final int count = mListeners.beginBroadcast();
+ for (int i = 0; i < count; i++) {
+ ISensorPrivacyListener listener = mListeners.getBroadcastItem(i);
+ try {
+ listener.onSensorPrivacyChanged(enabled);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Caught an exception notifying listener " + listener + ": ", e);
+ }
+ }
+ mListeners.finishBroadcast();
+ // Handle the state of all sensors managed by this service.
+ SensorState.handleSensorPrivacyToggled(mContext, enabled);
+ }
+ }
+
+ private final class DeathRecipient implements IBinder.DeathRecipient {
+
+ private ISensorPrivacyListener mListener;
+
+ DeathRecipient(ISensorPrivacyListener listener) {
+ mListener = listener;
+ try {
+ mListener.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ mSensorPrivacyServiceImpl.removeSensorPrivacyListener(mListener);
+ }
+
+ public void destroy() {
+ try {
+ mListener.asBinder().unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ }
+ }
+ }
+
+ /**
+ * Maintains the state of the sensors when sensor privacy is enabled to return them to their
+ * original state when sensor privacy is disabled.
+ */
+ private static final class SensorState {
+
+ private static Object sLock = new Object();
+ @GuardedBy("sLock")
+ private static SensorState sPreviousState;
+
+ private boolean mAirplaneEnabled;
+ private boolean mLocationEnabled;
+
+ SensorState(boolean airplaneEnabled, boolean locationEnabled) {
+ mAirplaneEnabled = airplaneEnabled;
+ mLocationEnabled = locationEnabled;
+ }
+
+ public static void handleSensorPrivacyToggled(Context context, boolean enabled) {
+ synchronized (sLock) {
+ SensorState state;
+ if (enabled) {
+ // if sensor privacy is being enabled then obtain the current state of the
+ // sensors to be persisted and restored when sensor privacy is disabled.
+ state = getCurrentSensorState(context);
+ } else {
+ // else obtain the previous sensor state to be restored, first from the saved
+ // state if available, otherwise attempt to read it from Settings.
+ if (sPreviousState != null) {
+ state = sPreviousState;
+ } else {
+ state = getPersistedSensorState(context);
+ }
+ // if the previous state is not available then return without attempting to
+ // modify the sensor state.
+ if (state == null) {
+ return;
+ }
+ }
+ // The SensorState represents the state of the sensor before sensor privacy was
+ // enabled; if airplane mode was not enabled then the state of airplane mode should
+ // be the same as the state of sensor privacy.
+ if (!state.mAirplaneEnabled) {
+ setAirplaneMode(context, enabled);
+ }
+ // Similar to airplane mode the state of location should be the opposite of sensor
+ // privacy mode, if it was enabled when sensor privacy was enabled then it should be
+ // disabled. If location is disabled when sensor privacy is enabled then it will be
+ // left disabled when sensor privacy is disabled.
+ if (state.mLocationEnabled) {
+ setLocationEnabled(context, !enabled);
+ }
+
+ // if sensor privacy is being enabled then persist the current state.
+ if (enabled) {
+ sPreviousState = state;
+ persistState(context, sPreviousState);
+ }
+ }
+ }
+
+ public static SensorState getCurrentSensorState(Context context) {
+ LocationManager locationManager = (LocationManager) context.getSystemService(
+ Context.LOCATION_SERVICE);
+ boolean airplaneEnabled = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
+ boolean locationEnabled = locationManager.isLocationEnabled();
+ return new SensorState(airplaneEnabled, locationEnabled);
+ }
+
+ public static void persistState(Context context, SensorState state) {
+ StringBuilder stateValue = new StringBuilder();
+ stateValue.append(state.mAirplaneEnabled
+ ? Settings.Secure.MAINTAIN_AIRPLANE_MODE_AFTER_SP_DISABLED
+ : Settings.Secure.DISABLE_AIRPLANE_MODE_AFTER_SP_DISABLED);
+ stateValue.append(",");
+ stateValue.append(
+ state.mLocationEnabled ? Settings.Secure.REENABLE_LOCATION_AFTER_SP_DISABLED
+ : Settings.Secure.MAINTAIN_LOCATION_AFTER_SP_DISABLED);
+ Settings.Secure.putString(context.getContentResolver(),
+ Settings.Secure.SENSOR_PRIVACY_SENSOR_STATE, stateValue.toString());
+ }
+
+ public static SensorState getPersistedSensorState(Context context) {
+ String persistedState = Settings.Secure.getString(context.getContentResolver(),
+ Settings.Secure.SENSOR_PRIVACY_SENSOR_STATE);
+ if (persistedState == null) {
+ Log.e(TAG, "The persisted sensor state could not be obtained from Settings");
+ return null;
+ }
+ String[] sensorStates = persistedState.split(",");
+ if (sensorStates.length < 2) {
+ Log.e(TAG, "The persisted sensor state does not contain the expected values: "
+ + persistedState);
+ return null;
+ }
+ boolean airplaneEnabled = sensorStates[0].equals(
+ Settings.Secure.MAINTAIN_AIRPLANE_MODE_AFTER_SP_DISABLED);
+ boolean locationEnabled = sensorStates[1].equals(
+ Settings.Secure.REENABLE_LOCATION_AFTER_SP_DISABLED);
+ return new SensorState(airplaneEnabled, locationEnabled);
+ }
+
+ private static void setAirplaneMode(Context context, boolean enable) {
+ ConnectivityManager connectivityManager =
+ (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ connectivityManager.setAirplaneMode(enable);
+ }
+
+ private static void setLocationEnabled(Context context, boolean enable) {
+ LocationManager locationManager = (LocationManager) context.getSystemService(
+ Context.LOCATION_SERVICE);
+ locationManager.setLocationEnabledForUser(enable,
+ UserHandle.of(ActivityManager.getCurrentUser()));
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 581d4350d28d..6c62725bb5bd 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -57,6 +57,7 @@ import android.app.KeyguardManager;
import android.app.admin.SecurityLog;
import android.app.usage.StorageStatsManager;
import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -777,6 +778,18 @@ class StorageManagerService extends IStorageManager.Stub
}
});
refreshZramSettings();
+
+ // Toggle isolated-enable system property in response to settings
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ISOLATED_STORAGE_REMOTE),
+ false /*notifyForDescendants*/,
+ new ContentObserver(null /* current thread */) {
+ @Override
+ public void onChange(boolean selfChange) {
+ refreshIsolatedStorageSettings();
+ }
+ });
+ refreshIsolatedStorageSettings();
}
/**
@@ -802,6 +815,32 @@ class StorageManagerService extends IStorageManager.Stub
}
}
+ private void refreshIsolatedStorageSettings() {
+ final int local = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.ISOLATED_STORAGE_LOCAL, 0);
+ final int remote = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.ISOLATED_STORAGE_REMOTE, 0);
+
+ // Walk down precedence chain; we prefer local settings first, then
+ // remote settings, before finally falling back to hard-coded default.
+ final boolean res;
+ if (local == -1) {
+ res = false;
+ } else if (local == 1) {
+ res = true;
+ } else if (remote == -1) {
+ res = false;
+ } else if (remote == 1) {
+ res = true;
+ } else {
+ res = false;
+ }
+
+ Slog.d(TAG, "Isolated storage local flag " + local + " and remote flag "
+ + remote + " resolved to " + res);
+ SystemProperties.set(StorageManager.PROP_ISOLATED_STORAGE, Boolean.toString(res));
+ }
+
/**
* MediaProvider has a ton of code that makes assumptions about storage
* paths never changing, so we outright kill them to pick up new state.
@@ -2208,18 +2247,22 @@ class StorageManagerService extends IStorageManager.Stub
}
}
- if ((mask & StorageManager.DEBUG_ISOLATED_STORAGE) != 0) {
- final boolean enabled = (flags & StorageManager.DEBUG_ISOLATED_STORAGE) != 0;
+ if ((mask & (StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_ON
+ | StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_OFF)) != 0) {
+ final int value;
+ if ((flags & StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_ON) != 0) {
+ value = 1;
+ } else if ((flags & StorageManager.DEBUG_ISOLATED_STORAGE_FORCE_OFF) != 0) {
+ value = -1;
+ } else {
+ value = 0;
+ }
final long token = Binder.clearCallingIdentity();
try {
- SystemProperties.set(StorageManager.PROP_ISOLATED_STORAGE,
- Boolean.toString(enabled));
-
- // Some of the storage related permissions get fiddled with during
- // package scanning. So, delete the package cache to force PackageManagerService
- // to do package scanning.
- FileUtils.deleteContents(Environment.getPackageCacheDirectory());
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.ISOLATED_STORAGE_LOCAL, value);
+ refreshIsolatedStorageSettings();
// Perform hard reboot to kick policy into place
mContext.getSystemService(PowerManager.class).reboot(null);
@@ -3758,6 +3801,8 @@ class StorageManagerService extends IStorageManager.Stub
pw.println();
pw.println("Primary storage UUID: " + mPrimaryStorageUuid);
+
+ pw.println();
final Pair<String, Long> pair = StorageManager.getPrimaryStoragePathAndSize();
if (pair == null) {
pw.println("Internal storage total size: N/A");
@@ -3770,8 +3815,18 @@ class StorageManagerService extends IStorageManager.Stub
pw.print(DataUnit.MEBIBYTES.toBytes(pair.second));
pw.println(" MiB)");
}
+
+ pw.println();
pw.println("Local unlocked users: " + Arrays.toString(mLocalUnlockedUsers));
pw.println("System unlocked users: " + Arrays.toString(mSystemUnlockedUsers));
+
+ final ContentResolver cr = mContext.getContentResolver();
+ pw.println();
+ pw.println("Isolated storage, local feature flag: "
+ + Settings.Global.getInt(cr, Settings.Global.ISOLATED_STORAGE_LOCAL, 0));
+ pw.println("Isolated storage, remote feature flag: "
+ + Settings.Global.getInt(cr, Settings.Global.ISOLATED_STORAGE_REMOTE, 0));
+ pw.println("Isolated storage, resolved: " + StorageManager.hasIsolatedStorage());
}
synchronized (mObbMounts) {
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index b04ae1746d18..b6c49217f4dc 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1996,6 +1996,13 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null);
}
+ if ((events & PhoneStateListener.LISTEN_PREFERRED_DATA_SUBID_CHANGE) != 0) {
+ // It can have either READ_PHONE_STATE or READ_PRIVILEGED_PHONE_STATE.
+ TelephonyPermissions.checkReadPhoneState(mContext,
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID, Binder.getCallingPid(),
+ Binder.getCallingUid(), callingPackage, "listen to "
+ + "LISTEN_PREFERRED_DATA_SUBID_CHANGE");
+ }
return true;
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 8751d24bcf67..5afb90d681af 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1964,7 +1964,8 @@ public final class ActiveServices {
+ " type=" + resolvedType + " callingUid=" + callingUid);
userId = mAm.mUserController.handleIncomingUser(callingPid, callingUid, userId, false,
- ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE, "service", null);
+ ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE, "service",
+ callingPackage);
ServiceMap smap = getServiceMapLocked(userId);
final ComponentName comp;
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 8571ae6b07f6..1c04a94be7df 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -69,6 +69,7 @@ final class ActivityManagerConstants extends ContentObserver {
static final String KEY_PROCESS_START_ASYNC = "process_start_async";
static final String KEY_MEMORY_INFO_THROTTLE_TIME = "memory_info_throttle_time";
static final String KEY_TOP_TO_FGS_GRACE_DURATION = "top_to_fgs_grace_duration";
+ static final String KEY_USE_COMPACTION = "use_compaction";
private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000;
@@ -99,6 +100,7 @@ final class ActivityManagerConstants extends ContentObserver {
private static final boolean DEFAULT_PROCESS_START_ASYNC = true;
private static final long DEFAULT_MEMORY_INFO_THROTTLE_TIME = 5*60*1000;
private static final long DEFAULT_TOP_TO_FGS_GRACE_DURATION = 15 * 1000;
+ private static final boolean DEFAULT_USE_COMPACTION = false;
// Maximum number of cached processes we will allow.
public int MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES;
@@ -218,6 +220,9 @@ final class ActivityManagerConstants extends ContentObserver {
// this long.
public long TOP_TO_FGS_GRACE_DURATION = DEFAULT_TOP_TO_FGS_GRACE_DURATION;
+ // Use compaction for background apps.
+ public boolean USE_COMPACTION = DEFAULT_USE_COMPACTION;
+
// Indicates whether the activity starts logging is enabled.
// Controlled by Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED
volatile boolean mFlagActivityStartsLoggingEnabled;
@@ -375,6 +380,7 @@ final class ActivityManagerConstants extends ContentObserver {
DEFAULT_MEMORY_INFO_THROTTLE_TIME);
TOP_TO_FGS_GRACE_DURATION = mParser.getDurationMillis(KEY_TOP_TO_FGS_GRACE_DURATION,
DEFAULT_TOP_TO_FGS_GRACE_DURATION);
+ USE_COMPACTION = mParser.getBoolean(KEY_USE_COMPACTION, DEFAULT_USE_COMPACTION);
updateMaxCachedProcesses();
}
@@ -465,6 +471,8 @@ final class ActivityManagerConstants extends ContentObserver {
pw.println(MEMORY_INFO_THROTTLE_TIME);
pw.print(" "); pw.print(KEY_TOP_TO_FGS_GRACE_DURATION); pw.print("=");
pw.println(TOP_TO_FGS_GRACE_DURATION);
+ pw.print(" "); pw.print(KEY_USE_COMPACTION); pw.print("=");
+ pw.println(USE_COMPACTION);
pw.println();
if (mOverrideMaxCachedProcesses >= 0) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 6700a530edc0..739dbbc65f6f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -795,6 +795,11 @@ public class ActivityManagerService extends IActivityManager.Stub
*/
final ArrayList<ProcessRecord> mPendingPssProcesses = new ArrayList<ProcessRecord>();
+ /**
+ * Processes to compact.
+ */
+ final ArrayList<ProcessRecord> mPendingCompactionProcesses = new ArrayList<ProcessRecord>();
+
private boolean mBinderTransactionTrackingEnabled = false;
/**
@@ -1452,6 +1457,7 @@ public class ActivityManagerService extends IActivityManager.Stub
final Handler mUiHandler;
final ServiceThread mProcStartHandlerThread;
final Handler mProcStartHandler;
+ final ServiceThread mCompactionThread;
final ActivityManagerConstants mConstants;
@@ -1789,6 +1795,11 @@ public class ActivityManagerService extends IActivityManager.Stub
}
};
+ static final int COMPACT_PROCESS_SOME = 1;
+ static final int COMPACT_PROCESS_FULL = 2;
+ static final int COMPACT_PROCESS_MSG = 1;
+ final Handler mCompactionHandler;
+
static final int COLLECT_PSS_BG_MSG = 1;
final Handler mBgHandler = new Handler(BackgroundThread.getHandler().getLooper()) {
@@ -2224,6 +2235,8 @@ public class ActivityManagerService extends IActivityManager.Stub
? new PendingIntentController(handlerThread.getLooper(), mUserController) : null;
mProcStartHandlerThread = null;
mProcStartHandler = null;
+ mCompactionThread = null;
+ mCompactionHandler = null;
mHiddenApiBlacklist = null;
mFactoryTest = FACTORY_TEST_OFF;
}
@@ -2252,6 +2265,88 @@ public class ActivityManagerService extends IActivityManager.Stub
mProcStartHandlerThread.start();
mProcStartHandler = new Handler(mProcStartHandlerThread.getLooper());
+ mCompactionThread = new ServiceThread("CompactionThread",
+ THREAD_PRIORITY_FOREGROUND, true);
+ mCompactionThread.start();
+ mCompactionHandler = new Handler(mCompactionThread.getLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case COMPACT_PROCESS_MSG: {
+ long start = SystemClock.uptimeMillis();
+ ProcessRecord proc;
+ int pid;
+ String action;
+ final String name;
+ int pendingAction, lastCompactAction;
+ long lastCompactTime;
+ synchronized(ActivityManagerService.this) {
+ proc = mPendingCompactionProcesses.remove(0);
+
+ // don't compact if the process has returned to perceptible
+ if (proc.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
+ return;
+ }
+
+ pid = proc.pid;
+ name = proc.processName;
+ pendingAction = proc.reqCompactAction;
+ lastCompactAction = proc.lastCompactAction;
+ lastCompactTime = proc.lastCompactTime;
+ }
+ if (pid == 0) {
+ // not a real process, either one being launched or one being killed
+ return;
+ }
+
+ // basic throttling
+ if (pendingAction == COMPACT_PROCESS_SOME) {
+ // if we're compacting some, then compact if >10s after last full
+ // or >5s after last some
+ if ((lastCompactAction == COMPACT_PROCESS_SOME && (start - lastCompactTime < 5000)) ||
+ (lastCompactAction == COMPACT_PROCESS_FULL && (start - lastCompactTime < 10000)))
+ return;
+ } else {
+ // if we're compacting full, then compact if >10s after last full
+ // or >.5s after last some
+ if ((lastCompactAction == COMPACT_PROCESS_SOME && (start - lastCompactTime < 500)) ||
+ (lastCompactAction == COMPACT_PROCESS_FULL && (start - lastCompactTime < 10000)))
+ return;
+ }
+
+ try {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact " +
+ ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full") +
+ ": " + name);
+ long[] rssBefore = Process.getRss(pid);
+ FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim");
+ if (pendingAction == COMPACT_PROCESS_SOME) {
+ action = "file";
+ } else {
+ action = "all";
+ }
+ fos.write(action.getBytes());
+ fos.close();
+ long[] rssAfter = Process.getRss(pid);
+ long end = SystemClock.uptimeMillis();
+ EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name, action,
+ rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
+ rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], end-start);
+ synchronized(ActivityManagerService.this) {
+ proc.lastCompactTime = end;
+ proc.lastCompactAction = pendingAction;
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ } catch (Exception e) {
+ // nothing to do, presumably the process died
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+ }
+ }
+ }
+ };
+
+
mConstants = new ActivityManagerConstants(this, mHandler);
mProcessList.init(this);
@@ -2345,13 +2440,15 @@ public class ActivityManagerService extends IActivityManager.Stub
Watchdog.getInstance().addMonitor(this);
Watchdog.getInstance().addThread(mHandler);
- // bind background thread to little cores
+ // bind background threads to little cores
// this is expected to fail inside of framework tests because apps can't touch cpusets directly
// make sure we've already adjusted system_server's internal view of itself first
updateOomAdjLocked();
try {
Process.setThreadGroupAndCpuset(BackgroundThread.get().getThreadId(),
- Process.THREAD_GROUP_BG_NONINTERACTIVE);
+ Process.THREAD_GROUP_SYSTEM);
+ Process.setThreadGroupAndCpuset(mCompactionThread.getThreadId(),
+ Process.THREAD_GROUP_SYSTEM);
} catch (Exception e) {
Slog.w(TAG, "Setting background thread cpuset failed");
}
@@ -16921,6 +17018,24 @@ public class ActivityManagerService extends IActivityManager.Stub
int changes = 0;
if (app.curAdj != app.setAdj) {
+ // don't compact during bootup
+ if (mConstants.USE_COMPACTION && mBooted) {
+ // Perform a minor compaction when a perceptible app becomes the prev/home app
+ // Perform a major compaction when any app enters cached
+ // reminder: here, setAdj is previous state, curAdj is upcoming state
+ if (app.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ &&
+ (app.curAdj == ProcessList.PREVIOUS_APP_ADJ ||
+ app.curAdj == ProcessList.HOME_APP_ADJ)) {
+ app.reqCompactAction = COMPACT_PROCESS_SOME;
+ mPendingCompactionProcesses.add(app);
+ mCompactionHandler.sendEmptyMessage(COMPACT_PROCESS_MSG);
+ } else if (app.setAdj < ProcessList.CACHED_APP_MIN_ADJ &&
+ app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ) {
+ app.reqCompactAction = COMPACT_PROCESS_FULL;
+ mPendingCompactionProcesses.add(app);
+ mCompactionHandler.sendEmptyMessage(COMPACT_PROCESS_MSG);
+ }
+ }
ProcessList.setOomAdj(app.pid, app.uid, app.curAdj);
if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mCurOomAdjUid == app.info.uid) {
String msg = "Set " + app.pid + " " + app.processName + " adj "
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index ab9ba0894436..a376e7a15410 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -724,6 +724,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub
synchronized (mStats) {
mStats.noteWifiOnLocked();
}
+ StatsLog.write(StatsLog.WIFI_ENABLED_STATE_CHANGED,
+ StatsLog.WIFI_ENABLED_STATE_CHANGED__STATE__ON);
}
public void noteWifiOff() {
@@ -731,6 +733,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub
synchronized (mStats) {
mStats.noteWifiOffLocked();
}
+ StatsLog.write(StatsLog.WIFI_ENABLED_STATE_CHANGED,
+ StatsLog.WIFI_ENABLED_STATE_CHANGED__STATE__OFF);
}
public void noteStartAudio(int uid) {
@@ -865,6 +869,9 @@ public final class BatteryStatsService extends IBatteryStats.Stub
synchronized (mStats) {
mStats.noteWifiRunningLocked(ws);
}
+ // TODO: Log WIFI_RUNNING_STATE_CHANGED in a better spot to include Hotspot too.
+ StatsLog.write(StatsLog.WIFI_RUNNING_STATE_CHANGED,
+ ws, StatsLog.WIFI_RUNNING_STATE_CHANGED__STATE__ON);
}
public void noteWifiRunningChanged(WorkSource oldWs, WorkSource newWs) {
@@ -872,6 +879,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub
synchronized (mStats) {
mStats.noteWifiRunningChangedLocked(oldWs, newWs);
}
+ StatsLog.write(StatsLog.WIFI_RUNNING_STATE_CHANGED,
+ newWs, StatsLog.WIFI_RUNNING_STATE_CHANGED__STATE__ON);
+ StatsLog.write(StatsLog.WIFI_RUNNING_STATE_CHANGED,
+ oldWs, StatsLog.WIFI_RUNNING_STATE_CHANGED__STATE__OFF);
}
public void noteWifiStopped(WorkSource ws) {
@@ -879,6 +890,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub
synchronized (mStats) {
mStats.noteWifiStoppedLocked(ws);
}
+ StatsLog.write(StatsLog.WIFI_RUNNING_STATE_CHANGED,
+ ws, StatsLog.WIFI_RUNNING_STATE_CHANGED__STATE__OFF);
}
public void noteWifiState(int wifiState, String accessPoint) {
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index 09064f2fc441..fa7a4c532f42 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -135,4 +135,7 @@ option java_package com.android.server.am
30062 am_on_activity_result_called (User|1|5),(Component Name|3),(Reason|3)
# The task is being removed from its parent stack
-30061 am_remove_task (Task ID|1|5), (Stack ID|1|5) \ No newline at end of file
+30061 am_remove_task (Task ID|1|5), (Stack ID|1|5)
+
+# The task is being compacted
+30063 am_compact (Pid|1|5),(Process Name|3),(Action|3),(BeforeRssTotal|2|2),(BeforeRssFile|2|2),(BeforeRssAnon|2|2),(BeforeRssSwap|2|2),(AfterRssTotal|2|2),(AfterRssFile|2|2),(AfterRssAnon|2|2),(AfterRssSwap|2|2),(Time|2|3) \ No newline at end of file
diff --git a/services/core/java/com/android/server/am/MemoryStatUtil.java b/services/core/java/com/android/server/am/MemoryStatUtil.java
index 90fe30c7c718..a58491472036 100644
--- a/services/core/java/com/android/server/am/MemoryStatUtil.java
+++ b/services/core/java/com/android/server/am/MemoryStatUtil.java
@@ -123,9 +123,8 @@ public final class MemoryStatUtil {
* if the file is not available.
*/
public static String readCmdlineFromProcfs(int pid) {
- String path = String.format(Locale.US, PROC_CMDLINE_FILE_FMT, pid);
- String cmdline = readFileContents(path);
- return cmdline != null ? cmdline : "";
+ final String path = String.format(Locale.US, PROC_CMDLINE_FILE_FMT, pid);
+ return parseCmdlineFromProcfs(readFileContents(path));
}
private static String readFileContents(String path) {
@@ -210,6 +209,24 @@ public final class MemoryStatUtil {
return m.find() ? Long.parseLong(m.group(1)) * BYTES_IN_KILOBYTE : 0;
}
+
+ /**
+ * Parses cmdline out of the contents of the /proc/pid/cmdline file in procfs.
+ *
+ * Parsing is required to strip anything after first null byte.
+ */
+ @VisibleForTesting
+ static String parseCmdlineFromProcfs(String cmdline) {
+ if (cmdline == null) {
+ return "";
+ }
+ int firstNullByte = cmdline.indexOf("\0");
+ if (firstNullByte == -1) {
+ return cmdline;
+ }
+ return cmdline.substring(0, firstNullByte);
+ }
+
/**
* Returns whether per-app memcg is available on device.
*/
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 4826f486da89..c4b715042953 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -145,6 +145,9 @@ final class ProcessRecord implements WindowProcessListener {
int curAdj; // Current OOM adjustment for this process
int setAdj; // Last set OOM adjustment for this process
int verifiedAdj; // The last adjustment that was verified as actually being set
+ long lastCompactTime; // The last time that this process was compacted
+ int reqCompactAction; // The most recent compaction action requested for this app.
+ int lastCompactAction; // The most recent compaction action performed for this app.
private int mCurSchedGroup; // Currently desired scheduling class
int setSchedGroup; // Last set to background scheduling class
int trimMemoryLevel; // Last selected memory trimming level
@@ -382,6 +385,8 @@ final class ProcessRecord implements WindowProcessListener {
pw.print(" setRaw="); pw.print(setRawAdj);
pw.print(" cur="); pw.print(curAdj);
pw.print(" set="); pw.println(setAdj);
+ pw.print(prefix); pw.print("lastCompactTime="); pw.print(lastCompactTime);
+ pw.print(" lastCompactAction="); pw.print(lastCompactAction);
pw.print(prefix); pw.print("mCurSchedGroup="); pw.print(mCurSchedGroup);
pw.print(" setSchedGroup="); pw.print(setSchedGroup);
pw.print(" systemNoUi="); pw.print(systemNoUi);
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index b97e90487123..78b3c15500ea 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -16,30 +16,26 @@
package com.android.server.display;
-import com.android.server.EventLogTags;
-import com.android.server.LocalServices;
-
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.display.BrightnessConfiguration;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
-import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.SystemClock;
import android.os.Trace;
-import android.text.format.DateUtils;
import android.util.EventLog;
import android.util.MathUtils;
import android.util.Slog;
import android.util.TimeUtils;
+import com.android.server.EventLogTags;
+
import java.io.PrintWriter;
class AutomaticBrightnessController {
@@ -127,7 +123,8 @@ class AutomaticBrightnessController {
private final int mWeightingIntercept;
// Configuration object for determining thresholds to change brightness dynamically
- private final HysteresisLevels mHysteresisLevels;
+ private final HysteresisLevels mAmbientBrightnessThresholds;
+ private final HysteresisLevels mScreenBrightnessThresholds;
// Amount of time to delay auto-brightness after screen on while waiting for
// the light sensor to warm-up in milliseconds.
@@ -147,8 +144,12 @@ class AutomaticBrightnessController {
private boolean mAmbientLuxValid;
// The ambient light level threshold at which to brighten or darken the screen.
- private float mBrighteningLuxThreshold;
- private float mDarkeningLuxThreshold;
+ private float mAmbientBrighteningThreshold;
+ private float mAmbientDarkeningThreshold;
+
+ // The screen brightness threshold at which to brighten or darken the screen.
+ private float mScreenBrighteningThreshold;
+ private float mScreenDarkeningThreshold;
// The most recent light sample.
private float mLastObservedLux;
@@ -196,7 +197,8 @@ class AutomaticBrightnessController {
int lightSensorWarmUpTime, int brightnessMin, int brightnessMax, float dozeScaleFactor,
int lightSensorRate, int initialLightSensorRate, long brighteningLightDebounceConfig,
long darkeningLightDebounceConfig, boolean resetAmbientLuxAfterWarmUpConfig,
- HysteresisLevels hysteresisLevels) {
+ HysteresisLevels ambientBrightnessThresholds,
+ HysteresisLevels screenBrightnessThresholds) {
mCallbacks = callbacks;
mSensorManager = sensorManager;
mBrightnessMapper = mapper;
@@ -212,7 +214,8 @@ class AutomaticBrightnessController {
mResetAmbientLuxAfterWarmUpConfig = resetAmbientLuxAfterWarmUpConfig;
mAmbientLightHorizon = AMBIENT_LIGHT_LONG_HORIZON_MILLIS;
mWeightingIntercept = AMBIENT_LIGHT_LONG_HORIZON_MILLIS;
- mHysteresisLevels = hysteresisLevels;
+ mAmbientBrightnessThresholds = ambientBrightnessThresholds;
+ mScreenBrightnessThresholds = screenBrightnessThresholds;
mShortTermModelValid = true;
mShortTermModelAnchor = -1;
@@ -364,8 +367,10 @@ class AutomaticBrightnessController {
pw.println(" mCurrentLightSensorRate=" + mCurrentLightSensorRate);
pw.println(" mAmbientLux=" + mAmbientLux);
pw.println(" mAmbientLuxValid=" + mAmbientLuxValid);
- pw.println(" mBrighteningLuxThreshold=" + mBrighteningLuxThreshold);
- pw.println(" mDarkeningLuxThreshold=" + mDarkeningLuxThreshold);
+ pw.println(" mAmbientBrighteningThreshold=" + mAmbientBrighteningThreshold);
+ pw.println(" mAmbientDarkeningThreshold=" + mAmbientDarkeningThreshold);
+ pw.println(" mScreenBrighteningThreshold=" + mScreenBrighteningThreshold);
+ pw.println(" mScreenDarkeningThreshold=" + mScreenDarkeningThreshold);
pw.println(" mLastObservedLux=" + mLastObservedLux);
pw.println(" mLastObservedLuxTime=" + TimeUtils.formatUptime(mLastObservedLuxTime));
pw.println(" mRecentLightSamples=" + mRecentLightSamples);
@@ -384,7 +389,8 @@ class AutomaticBrightnessController {
mBrightnessMapper.dump(pw);
pw.println();
- mHysteresisLevels.dump(pw);
+ mAmbientBrightnessThresholds.dump(pw);
+ mScreenBrightnessThresholds.dump(pw);
}
private boolean setLightSensorEnabled(boolean enable) {
@@ -460,8 +466,8 @@ class AutomaticBrightnessController {
lux = 0;
}
mAmbientLux = lux;
- mBrighteningLuxThreshold = mHysteresisLevels.getBrighteningThreshold(lux);
- mDarkeningLuxThreshold = mHysteresisLevels.getDarkeningThreshold(lux);
+ mAmbientBrighteningThreshold = mAmbientBrightnessThresholds.getBrighteningThreshold(lux);
+ mAmbientDarkeningThreshold = mAmbientBrightnessThresholds.getDarkeningThreshold(lux);
// If the short term model was invalidated and the change is drastic enough, reset it.
if (!mShortTermModelValid && mShortTermModelAnchor != -1) {
@@ -552,7 +558,7 @@ class AutomaticBrightnessController {
final int N = mAmbientLightRingBuffer.size();
long earliestValidTime = time;
for (int i = N - 1; i >= 0; i--) {
- if (mAmbientLightRingBuffer.getLux(i) <= mBrighteningLuxThreshold) {
+ if (mAmbientLightRingBuffer.getLux(i) <= mAmbientBrighteningThreshold) {
break;
}
earliestValidTime = mAmbientLightRingBuffer.getTime(i);
@@ -564,7 +570,7 @@ class AutomaticBrightnessController {
final int N = mAmbientLightRingBuffer.size();
long earliestValidTime = time;
for (int i = N - 1; i >= 0; i--) {
- if (mAmbientLightRingBuffer.getLux(i) >= mDarkeningLuxThreshold) {
+ if (mAmbientLightRingBuffer.getLux(i) >= mAmbientDarkeningThreshold) {
break;
}
earliestValidTime = mAmbientLightRingBuffer.getTime(i);
@@ -617,20 +623,19 @@ class AutomaticBrightnessController {
float slowAmbientLux = calculateAmbientLux(time, AMBIENT_LIGHT_LONG_HORIZON_MILLIS);
float fastAmbientLux = calculateAmbientLux(time, AMBIENT_LIGHT_SHORT_HORIZON_MILLIS);
- if ((slowAmbientLux >= mBrighteningLuxThreshold &&
- fastAmbientLux >= mBrighteningLuxThreshold &&
- nextBrightenTransition <= time)
- ||
- (slowAmbientLux <= mDarkeningLuxThreshold &&
- fastAmbientLux <= mDarkeningLuxThreshold &&
- nextDarkenTransition <= time)) {
+ if ((slowAmbientLux >= mAmbientBrighteningThreshold
+ && fastAmbientLux >= mAmbientBrighteningThreshold
+ && nextBrightenTransition <= time)
+ || (slowAmbientLux <= mAmbientDarkeningThreshold
+ && fastAmbientLux <= mAmbientDarkeningThreshold
+ && nextDarkenTransition <= time)) {
setAmbientLux(fastAmbientLux);
if (DEBUG) {
- Slog.d(TAG, "updateAmbientLux: " +
- ((fastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": " +
- "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold + ", " +
- "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", " +
- "mAmbientLux=" + mAmbientLux);
+ Slog.d(TAG, "updateAmbientLux: "
+ + ((fastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": "
+ + "mAmbientBrighteningThreshold=" + mAmbientBrighteningThreshold + ", "
+ + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", "
+ + "mAmbientLux=" + mAmbientLux);
}
updateAutoBrightness(true);
nextBrightenTransition = nextAmbientLightBrighteningTransition(time);
@@ -661,7 +666,22 @@ class AutomaticBrightnessController {
int newScreenAutoBrightness =
clampScreenBrightness(Math.round(value * PowerManager.BRIGHTNESS_ON));
+
+ // If screenAutoBrightness is set, we should have screen{Brightening,Darkening}Threshold,
+ // in which case we ignore the new screen brightness if it doesn't differ enough from the
+ // previous one.
+ if (mScreenAutoBrightness != -1
+ && newScreenAutoBrightness > mScreenDarkeningThreshold
+ && newScreenAutoBrightness < mScreenBrighteningThreshold) {
+ if (DEBUG) {
+ Slog.d(TAG, "ignoring newScreenAutoBrightness: " + mScreenDarkeningThreshold
+ + " < " + newScreenAutoBrightness + " < " + mScreenBrighteningThreshold);
+ }
+ return;
+ }
+
if (mScreenAutoBrightness != newScreenAutoBrightness) {
+
if (DEBUG) {
Slog.d(TAG, "updateAutoBrightness: " +
"mScreenAutoBrightness=" + mScreenAutoBrightness + ", " +
@@ -669,6 +689,11 @@ class AutomaticBrightnessController {
}
mScreenAutoBrightness = newScreenAutoBrightness;
+ mScreenBrighteningThreshold =
+ mScreenBrightnessThresholds.getBrighteningThreshold(newScreenAutoBrightness);
+ mScreenDarkeningThreshold =
+ mScreenBrightnessThresholds.getDarkeningThreshold(newScreenAutoBrightness);
+
if (sendUpdate) {
mCallbacks.updateBrightness();
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index e2c8ef982eb3..249270bfda7e 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -16,16 +16,11 @@
package com.android.server.display;
-import android.app.ActivityManager;
-import com.android.internal.app.IBatteryStats;
-import com.android.server.LocalServices;
-import com.android.server.am.BatteryStatsService;
-import com.android.server.policy.WindowManagerPolicy;
-
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.content.res.Resources;
@@ -54,6 +49,11 @@ import android.util.Slog;
import android.util.TimeUtils;
import android.view.Display;
+import com.android.internal.app.IBatteryStats;
+import com.android.server.LocalServices;
+import com.android.server.am.BatteryStatsService;
+import com.android.server.policy.WindowManagerPolicy;
+
import java.io.PrintWriter;
/**
@@ -422,14 +422,24 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor,
1, 1);
- int[] brightLevels = resources.getIntArray(
- com.android.internal.R.array.config_dynamicHysteresisBrightLevels);
- int[] darkLevels = resources.getIntArray(
- com.android.internal.R.array.config_dynamicHysteresisDarkLevels);
- int[] luxHysteresisLevels = resources.getIntArray(
- com.android.internal.R.array.config_dynamicHysteresisLuxLevels);
- HysteresisLevels hysteresisLevels = new HysteresisLevels(
- brightLevels, darkLevels, luxHysteresisLevels);
+ int[] ambientBrighteningThresholds = resources.getIntArray(
+ com.android.internal.R.array.config_ambientBrighteningThresholds);
+ int[] ambientDarkeningThresholds = resources.getIntArray(
+ com.android.internal.R.array.config_ambientDarkeningThresholds);
+ int[] ambientThresholdLevels = resources.getIntArray(
+ com.android.internal.R.array.config_ambientThresholdLevels);
+ HysteresisLevels ambientBrightnessThresholds = new HysteresisLevels(
+ ambientBrighteningThresholds, ambientDarkeningThresholds,
+ ambientThresholdLevels);
+
+ int[] screenBrighteningThresholds = resources.getIntArray(
+ com.android.internal.R.array.config_screenBrighteningThresholds);
+ int[] screenDarkeningThresholds = resources.getIntArray(
+ com.android.internal.R.array.config_screenDarkeningThresholds);
+ int[] screenThresholdLevels = resources.getIntArray(
+ com.android.internal.R.array.config_screenThresholdLevels);
+ HysteresisLevels screenBrightnessThresholds = new HysteresisLevels(
+ screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels);
long brighteningLightDebounce = resources.getInteger(
com.android.internal.R.integer.config_autoBrightnessBrighteningLightDebounce);
@@ -459,7 +469,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
lightSensorWarmUpTimeConfig, mScreenBrightnessRangeMinimum,
mScreenBrightnessRangeMaximum, dozeScaleFactor, lightSensorRate,
initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce,
- autoBrightnessResetAmbientLuxAfterWarmUp, hysteresisLevels);
+ autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds,
+ screenBrightnessThresholds);
} else {
mUseSoftwareAutoBrightnessConfig = false;
}
diff --git a/services/core/java/com/android/server/display/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java
index 1c02dd1fcdf4..2db1d03893d2 100644
--- a/services/core/java/com/android/server/display/HysteresisLevels.java
+++ b/services/core/java/com/android/server/display/HysteresisLevels.java
@@ -28,67 +28,67 @@ final class HysteresisLevels {
private static final String TAG = "HysteresisLevels";
// Default hysteresis constraints for brightening or darkening.
- // The recent lux must have changed by at least this fraction relative to the
- // current ambient lux before a change will be considered.
+ // The recent value must have changed by at least this fraction relative to the
+ // current value before a change will be considered.
private static final float DEFAULT_BRIGHTENING_HYSTERESIS = 0.10f;
private static final float DEFAULT_DARKENING_HYSTERESIS = 0.20f;
private static final boolean DEBUG = false;
- private final float[] mBrightLevels;
- private final float[] mDarkLevels;
- private final float[] mLuxLevels;
+ private final float[] mBrighteningThresholds;
+ private final float[] mDarkeningThresholds;
+ private final float[] mThresholdLevels;
- /**
- * Creates a {@code HysteresisLevels} object with the given equal-length
- * integer arrays.
- * @param brightLevels an array of brightening hysteresis constraint constants
- * @param darkLevels an array of darkening hysteresis constraint constants
- * @param luxLevels a monotonically increasing array of illuminance
- * thresholds in units of lux
- */
- public HysteresisLevels(int[] brightLevels, int[] darkLevels, int[] luxLevels) {
- if (brightLevels.length != darkLevels.length || darkLevels.length != luxLevels.length + 1) {
+ /**
+ * Creates a {@code HysteresisLevels} object with the given equal-length
+ * integer arrays.
+ * @param brighteningThresholds an array of brightening hysteresis constraint constants.
+ * @param darkeningThresholds an array of darkening hysteresis constraint constants.
+ * @param thresholdLevels a monotonically increasing array of threshold levels.
+ */
+ HysteresisLevels(int[] brighteningThresholds, int[] darkeningThresholds,
+ int[] thresholdLevels) {
+ if (brighteningThresholds.length != darkeningThresholds.length
+ || darkeningThresholds.length != thresholdLevels.length + 1) {
throw new IllegalArgumentException("Mismatch between hysteresis array lengths.");
}
- mBrightLevels = setArrayFormat(brightLevels, 1000.0f);
- mDarkLevels = setArrayFormat(darkLevels, 1000.0f);
- mLuxLevels = setArrayFormat(luxLevels, 1.0f);
+ mBrighteningThresholds = setArrayFormat(brighteningThresholds, 1000.0f);
+ mDarkeningThresholds = setArrayFormat(darkeningThresholds, 1000.0f);
+ mThresholdLevels = setArrayFormat(thresholdLevels, 1.0f);
}
/**
- * Return the brightening hysteresis threshold for the given lux level.
+ * Return the brightening hysteresis threshold for the given value level.
*/
- public float getBrighteningThreshold(float lux) {
- float brightConstant = getReferenceLevel(lux, mBrightLevels);
- float brightThreshold = lux * (1.0f + brightConstant);
+ float getBrighteningThreshold(float value) {
+ float brightConstant = getReferenceLevel(value, mBrighteningThresholds);
+ float brightThreshold = value * (1.0f + brightConstant);
if (DEBUG) {
- Slog.d(TAG, "bright hysteresis constant=: " + brightConstant + ", threshold="
- + brightThreshold + ", lux=" + lux);
+ Slog.d(TAG, "bright hysteresis constant=" + brightConstant + ", threshold="
+ + brightThreshold + ", value=" + value);
}
return brightThreshold;
}
/**
- * Return the darkening hysteresis threshold for the given lux level.
+ * Return the darkening hysteresis threshold for the given value level.
*/
- public float getDarkeningThreshold(float lux) {
- float darkConstant = getReferenceLevel(lux, mDarkLevels);
- float darkThreshold = lux * (1.0f - darkConstant);
+ float getDarkeningThreshold(float value) {
+ float darkConstant = getReferenceLevel(value, mDarkeningThresholds);
+ float darkThreshold = value * (1.0f - darkConstant);
if (DEBUG) {
Slog.d(TAG, "dark hysteresis constant=: " + darkConstant + ", threshold="
- + darkThreshold + ", lux=" + lux);
+ + darkThreshold + ", value=" + value);
}
return darkThreshold;
}
/**
- * Return the hysteresis constant for the closest lux threshold value to the
- * current illuminance from the given array.
+ * Return the hysteresis constant for the closest threshold value from the given array.
*/
- private float getReferenceLevel(float lux, float[] referenceLevels) {
+ private float getReferenceLevel(float value, float[] referenceLevels) {
int index = 0;
- while (mLuxLevels.length > index && lux >= mLuxLevels[index]) {
+ while (mThresholdLevels.length > index && value >= mThresholdLevels[index]) {
++index;
}
return referenceLevels[index];
@@ -105,10 +105,10 @@ final class HysteresisLevels {
return levelArray;
}
- public void dump(PrintWriter pw) {
+ void dump(PrintWriter pw) {
pw.println("HysteresisLevels");
- pw.println(" mBrightLevels=" + Arrays.toString(mBrightLevels));
- pw.println(" mDarkLevels=" + Arrays.toString(mDarkLevels));
- pw.println(" mLuxLevels=" + Arrays.toString(mLuxLevels));
+ pw.println(" mBrighteningThresholds=" + Arrays.toString(mBrighteningThresholds));
+ pw.println(" mDarkeningThresholds=" + Arrays.toString(mDarkeningThresholds));
+ pw.println(" mThresholdLevels=" + Arrays.toString(mThresholdLevels));
}
}
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 1325f049e737..10dc156fbc01 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -67,6 +67,7 @@ import android.os.SystemClock;
import android.os.Temperature;
import android.os.UserHandle;
import android.os.UserManagerInternal;
+import android.os.WorkSource;
import android.provider.Settings;
import android.text.format.DateUtils;
import android.util.KeyValueListParser;
@@ -364,6 +365,8 @@ public class JobSchedulerService extends com.android.server.SystemService
private static final String KEY_CONN_CONGESTION_DELAY_FRAC = "conn_congestion_delay_frac";
private static final String KEY_CONN_PREFETCH_RELAX_FRAC = "conn_prefetch_relax_frac";
private static final String KEY_USE_HEARTBEATS = "use_heartbeats";
+ private static final String KEY_TIME_CONTROLLER_SKIP_NOT_READY_JOBS =
+ "tc_skip_not_ready_jobs";
private static final String KEY_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS =
"qc_allowed_time_per_period_ms";
private static final String KEY_QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS =
@@ -376,6 +379,8 @@ public class JobSchedulerService extends com.android.server.SystemService
"qc_window_size_frequent_ms";
private static final String KEY_QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS =
"qc_window_size_rare_ms";
+ private static final String KEY_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS =
+ "qc_max_execution_time_ms";
private static final int DEFAULT_MIN_IDLE_COUNT = 1;
private static final int DEFAULT_MIN_CHARGING_COUNT = 1;
@@ -402,6 +407,7 @@ public class JobSchedulerService extends com.android.server.SystemService
private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f;
private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f;
private static final boolean DEFAULT_USE_HEARTBEATS = true;
+ private static final boolean DEFAULT_TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false;
private static final long DEFAULT_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS =
10 * 60 * 1000L; // 10 minutes
private static final long DEFAULT_QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS =
@@ -414,6 +420,8 @@ public class JobSchedulerService extends com.android.server.SystemService
8 * 60 * 60 * 1000L; // 8 hours
private static final long DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS =
24 * 60 * 60 * 1000L; // 24 hours
+ private static final long DEFAULT_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS =
+ 4 * 60 * 60 * 1000L; // 4 hours
/**
* Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
@@ -538,6 +546,13 @@ public class JobSchedulerService extends com.android.server.SystemService
*/
public boolean USE_HEARTBEATS = DEFAULT_USE_HEARTBEATS;
+ /**
+ * Whether or not TimeController should skip setting wakeup alarms for jobs that aren't
+ * ready now.
+ */
+ public boolean TIME_CONTROLLER_SKIP_NOT_READY_JOBS =
+ DEFAULT_TIME_CONTROLLER_SKIP_NOT_READY_JOBS;
+
/** How much time each app will have to run jobs within their standby bucket window. */
public long QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS =
DEFAULT_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS;
@@ -581,6 +596,12 @@ public class JobSchedulerService extends com.android.server.SystemService
public long QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS =
DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS;
+ /**
+ * The maximum amount of time an app can have its jobs running within a 24 hour window.
+ */
+ public long QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS =
+ DEFAULT_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS;
+
private final KeyValueListParser mParser = new KeyValueListParser(',');
void updateConstantsLocked(String value) {
@@ -653,6 +674,9 @@ public class JobSchedulerService extends com.android.server.SystemService
CONN_PREFETCH_RELAX_FRAC = mParser.getFloat(KEY_CONN_PREFETCH_RELAX_FRAC,
DEFAULT_CONN_PREFETCH_RELAX_FRAC);
USE_HEARTBEATS = mParser.getBoolean(KEY_USE_HEARTBEATS, DEFAULT_USE_HEARTBEATS);
+ TIME_CONTROLLER_SKIP_NOT_READY_JOBS = mParser.getBoolean(
+ KEY_TIME_CONTROLLER_SKIP_NOT_READY_JOBS,
+ DEFAULT_TIME_CONTROLLER_SKIP_NOT_READY_JOBS);
QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = mParser.getDurationMillis(
KEY_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS,
DEFAULT_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS);
@@ -671,6 +695,9 @@ public class JobSchedulerService extends com.android.server.SystemService
QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = mParser.getDurationMillis(
KEY_QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS,
DEFAULT_QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS);
+ QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = mParser.getDurationMillis(
+ KEY_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS,
+ DEFAULT_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS);
}
void dump(IndentingPrintWriter pw) {
@@ -705,6 +732,8 @@ public class JobSchedulerService extends com.android.server.SystemService
pw.printPair(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println();
pw.printPair(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println();
pw.printPair(KEY_USE_HEARTBEATS, USE_HEARTBEATS).println();
+ pw.printPair(KEY_TIME_CONTROLLER_SKIP_NOT_READY_JOBS,
+ TIME_CONTROLLER_SKIP_NOT_READY_JOBS).println();
pw.printPair(KEY_QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS,
QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS).println();
pw.printPair(KEY_QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS,
@@ -717,6 +746,8 @@ public class JobSchedulerService extends com.android.server.SystemService
QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS).println();
pw.printPair(KEY_QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS,
QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS).println();
+ pw.printPair(KEY_QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS,
+ QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS).println();
pw.decreaseIndent();
}
@@ -748,6 +779,11 @@ public class JobSchedulerService extends com.android.server.SystemService
proto.write(ConstantsProto.CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC);
proto.write(ConstantsProto.USE_HEARTBEATS, USE_HEARTBEATS);
+ final long tcToken = proto.start(ConstantsProto.TIME_CONTROLLER);
+ proto.write(ConstantsProto.TimeController.SKIP_NOT_READY_JOBS,
+ TIME_CONTROLLER_SKIP_NOT_READY_JOBS);
+ proto.end(tcToken);
+
final long qcToken = proto.start(ConstantsProto.QUOTA_CONTROLLER);
proto.write(ConstantsProto.QuotaController.ALLOWED_TIME_PER_PERIOD_MS,
QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS);
@@ -761,6 +797,8 @@ public class JobSchedulerService extends com.android.server.SystemService
QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS);
proto.write(ConstantsProto.QuotaController.RARE_WINDOW_SIZE_MS,
QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS);
+ proto.write(ConstantsProto.QuotaController.MAX_EXECUTION_TIME_MS,
+ QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS);
proto.end(qcToken);
proto.end(token);
@@ -952,9 +990,15 @@ public class JobSchedulerService extends com.android.server.SystemService
return mConstants;
}
+ public boolean isChainedAttributionEnabled() {
+ return WorkSource.isChainedBatteryAttributionEnabled(getContext());
+ }
+
@Override
public void onStartUser(int userHandle) {
- mStartedUsers = ArrayUtils.appendInt(mStartedUsers, userHandle);
+ synchronized (mLock) {
+ mStartedUsers = ArrayUtils.appendInt(mStartedUsers, userHandle);
+ }
// Let's kick any outstanding jobs for this user.
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
}
@@ -967,7 +1011,9 @@ public class JobSchedulerService extends com.android.server.SystemService
@Override
public void onStopUser(int userHandle) {
- mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userHandle);
+ synchronized (mLock) {
+ mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userHandle);
+ }
}
/**
@@ -2150,8 +2196,7 @@ public class JobSchedulerService extends com.android.server.SystemService
final boolean jobExists = mJobs.containsJob(job);
- final int userId = job.getUserId();
- final boolean userStarted = ArrayUtils.contains(mStartedUsers, userId);
+ final boolean userStarted = areUsersStartedLocked(job);
if (DEBUG) {
Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
@@ -2239,7 +2284,7 @@ public class JobSchedulerService extends com.android.server.SystemService
try {
componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
- userId) != null);
+ job.getUserId()) != null);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
@@ -3052,6 +3097,13 @@ public class JobSchedulerService extends com.android.server.SystemService
printed = true;
pw.println("user-stopped");
}
+ if (!ArrayUtils.contains(mStartedUsers, js.getSourceUserId())) {
+ if (printed) {
+ pw.print(" ");
+ }
+ printed = true;
+ pw.println("source-user-stopped");
+ }
if (mBackingUpUids.indexOfKey(js.getSourceUid()) >= 0) {
if (printed) {
pw.print(" ");
@@ -3203,7 +3255,7 @@ public class JobSchedulerService extends com.android.server.SystemService
pw.print(" (job=");
pw.print(job.isReady());
pw.print(" user=");
- pw.print(ArrayUtils.contains(mStartedUsers, job.getUserId()));
+ pw.print(areUsersStartedLocked(job));
pw.print(" !pending=");
pw.print(!mPendingJobs.contains(job));
pw.print(" !active=");
@@ -3373,7 +3425,7 @@ public class JobSchedulerService extends com.android.server.SystemService
proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_READY,
job.isReady());
proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_USER_STARTED,
- ArrayUtils.contains(mStartedUsers, job.getUserId()));
+ areUsersStartedLocked(job));
proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_PENDING,
mPendingJobs.contains(job));
proto.write(JobSchedulerServiceDumpProto.RegisteredJob.IS_JOB_CURRENTLY_ACTIVE,
diff --git a/services/core/java/com/android/server/job/controllers/QuotaController.java b/services/core/java/com/android/server/job/controllers/QuotaController.java
index 58ee21795d99..ac2dbdf9450e 100644
--- a/services/core/java/com/android/server/job/controllers/QuotaController.java
+++ b/services/core/java/com/android/server/job/controllers/QuotaController.java
@@ -54,14 +54,13 @@ import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateControllerProto;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
- * Controller that tracks whether a package has exceeded its standby bucket quota.
+ * Controller that tracks whether an app has exceeded its standby bucket quota.
*
* Each job in each bucket is given 10 minutes to run within its respective time window. Active
* jobs can run indefinitely, working set jobs can run for 10 minutes within a 2 hour window,
@@ -203,6 +202,80 @@ public final class QuotaController extends StateController {
}
}
+ private static int hashLong(long val) {
+ return (int) (val ^ (val >>> 32));
+ }
+
+ @VisibleForTesting
+ static class ExecutionStats {
+ /**
+ * The time at which this record should be considered invalid, in the elapsed realtime
+ * timebase.
+ */
+ public long invalidTimeElapsed;
+
+ public long windowSizeMs;
+
+ /** The total amount of time the app ran in its respective bucket window size. */
+ public long executionTimeInWindowMs;
+ public int bgJobCountInWindow;
+
+ /** The total amount of time the app ran in the last {@link MAX_PERIOD_MS}. */
+ public long executionTimeInMaxPeriodMs;
+ public int bgJobCountInMaxPeriod;
+
+ /**
+ * The time after which the sum of all the app's sessions plus {@link mQuotaBufferMs} equals
+ * the quota. This is only valid if
+ * executionTimeInWindowMs >= {@link mAllowedTimePerPeriodMs} or
+ * executionTimeInMaxPeriodMs >= {@link mMaxExecutionTimeMs}.
+ */
+ public long quotaCutoffTimeElapsed;
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("invalidTime=").append(invalidTimeElapsed).append(", ")
+ .append("windowSize=").append(windowSizeMs).append(", ")
+ .append("executionTimeInWindow=").append(executionTimeInWindowMs).append(", ")
+ .append("bgJobCountInWindow=").append(bgJobCountInWindow).append(", ")
+ .append("executionTimeInMaxPeriod=").append(executionTimeInMaxPeriodMs)
+ .append(", ")
+ .append("bgJobCountInMaxPeriod=").append(bgJobCountInMaxPeriod).append(", ")
+ .append("quotaCutoffTime=").append(quotaCutoffTimeElapsed)
+ .toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ExecutionStats) {
+ ExecutionStats other = (ExecutionStats) obj;
+ return this.invalidTimeElapsed == other.invalidTimeElapsed
+ && this.windowSizeMs == other.windowSizeMs
+ && this.executionTimeInWindowMs == other.executionTimeInWindowMs
+ && this.bgJobCountInWindow == other.bgJobCountInWindow
+ && this.executionTimeInMaxPeriodMs == other.executionTimeInMaxPeriodMs
+ && this.bgJobCountInMaxPeriod == other.bgJobCountInMaxPeriod
+ && this.quotaCutoffTimeElapsed == other.quotaCutoffTimeElapsed;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 0;
+ result = 31 * result + hashLong(invalidTimeElapsed);
+ result = 31 * result + hashLong(windowSizeMs);
+ result = 31 * result + hashLong(executionTimeInWindowMs);
+ result = 31 * result + bgJobCountInWindow;
+ result = 31 * result + hashLong(executionTimeInMaxPeriodMs);
+ result = 31 * result + bgJobCountInMaxPeriod;
+ result = 31 * result + hashLong(quotaCutoffTimeElapsed);
+ return result;
+ }
+ }
+
/** List of all tracked jobs keyed by source package-userId combo. */
private final UserPackageMap<ArraySet<JobStatus>> mTrackedJobs = new UserPackageMap<>();
@@ -218,6 +291,9 @@ public final class QuotaController extends StateController {
*/
private final UserPackageMap<QcAlarmListener> mInQuotaAlarmListeners = new UserPackageMap<>();
+ /** Cached calculation results for each app, with the standby buckets as the array indices. */
+ private final UserPackageMap<ExecutionStats[]> mExecutionStatsCache = new UserPackageMap<>();
+
private final AlarmManager mAlarmManager;
private final ChargingTracker mChargeTracker;
private final Handler mHandler;
@@ -235,11 +311,29 @@ public final class QuotaController extends StateController {
private long mAllowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS;
/**
- * How much time the package should have before transitioning from out-of-quota to in-quota.
- * This should not affect processing if the package is already in-quota.
+ * The maximum amount of time an app can have its jobs running within a {@link MAX_PERIOD_MS}
+ * window.
+ */
+ private long mMaxExecutionTimeMs = 4 * 60 * MINUTE_IN_MILLIS;
+
+ /**
+ * How much time the app should have before transitioning from out-of-quota to in-quota.
+ * This should not affect processing if the app is already in-quota.
*/
private long mQuotaBufferMs = 30 * 1000L; // 30 seconds
+ /**
+ * {@link mAllowedTimePerPeriodMs} - {@link mQuotaBufferMs}. This can be used to determine when
+ * an app will have enough quota to transition from out-of-quota to in-quota.
+ */
+ private long mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs;
+
+ /**
+ * {@link mMaxExecutionTimeMs} - {@link mQuotaBufferMs}. This can be used to determine when an
+ * app will have enough quota to transition from out-of-quota to in-quota.
+ */
+ private long mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
+
private long mNextCleanupTimeElapsed = 0;
private final AlarmManager.OnAlarmListener mSessionCleanupAlarmListener =
new AlarmManager.OnAlarmListener() {
@@ -263,7 +357,7 @@ public final class QuotaController extends StateController {
/** The maximum period any bucket can have. */
private static final long MAX_PERIOD_MS = 24 * 60 * MINUTE_IN_MILLIS;
- /** A package has reached its quota. The message should contain a {@link Package} object. */
+ /** An app has reached its quota. The message should contain a {@link Package} object. */
private static final int MSG_REACHED_QUOTA = 0;
/** Drop any old timing sessions. */
private static final int MSG_CLEAN_UP_SESSIONS = 1;
@@ -341,12 +435,15 @@ public final class QuotaController extends StateController {
Math.max(MINUTE_IN_MILLIS, mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS));
if (mAllowedTimePerPeriodMs != newAllowedTimeMs) {
mAllowedTimePerPeriodMs = newAllowedTimeMs;
+ mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs;
changed = true;
}
long newQuotaBufferMs = Math.max(0,
Math.min(5 * MINUTE_IN_MILLIS, mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS));
if (mQuotaBufferMs != newQuotaBufferMs) {
mQuotaBufferMs = newQuotaBufferMs;
+ mAllowedTimeIntoQuotaMs = mAllowedTimePerPeriodMs - mQuotaBufferMs;
+ mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
changed = true;
}
long newActivePeriodMs = Math.max(mAllowedTimePerPeriodMs,
@@ -373,6 +470,13 @@ public final class QuotaController extends StateController {
mBucketPeriodsMs[RARE_INDEX] = newRarePeriodMs;
changed = true;
}
+ long newMaxExecutionTimeMs = Math.max(60 * MINUTE_IN_MILLIS,
+ Math.min(MAX_PERIOD_MS, mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS));
+ if (mMaxExecutionTimeMs != newMaxExecutionTimeMs) {
+ mMaxExecutionTimeMs = newMaxExecutionTimeMs;
+ mMaxExecutionTimeIntoQuotaMs = mMaxExecutionTimeMs - mQuotaBufferMs;
+ changed = true;
+ }
if (changed) {
// Update job bookkeeping out of band.
@@ -406,6 +510,7 @@ public final class QuotaController extends StateController {
mAlarmManager.cancel(alarmListener);
mInQuotaAlarmListeners.delete(userId, packageName);
}
+ mExecutionStatsCache.delete(userId, packageName);
}
@Override
@@ -414,6 +519,7 @@ public final class QuotaController extends StateController {
mPkgTimers.delete(userId);
mTimingSessions.delete(userId);
mInQuotaAlarmListeners.delete(userId);
+ mExecutionStatsCache.delete(userId);
}
/**
@@ -439,7 +545,6 @@ public final class QuotaController extends StateController {
private boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName,
final int standbyBucket) {
if (standbyBucket == NEVER_INDEX) return false;
- if (standbyBucket == ACTIVE_INDEX) return true;
// This check is needed in case the flag is toggled after a job has been registered.
if (!mShouldThrottle) return true;
@@ -472,46 +577,152 @@ public final class QuotaController extends StateController {
if (standbyBucket == NEVER_INDEX) {
return 0;
}
+ final ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
+ return Math.min(mAllowedTimePerPeriodMs - stats.executionTimeInWindowMs,
+ mMaxExecutionTimeMs - stats.executionTimeInMaxPeriodMs);
+ }
+
+ /** Returns the execution stats of the app in the most recent window. */
+ @VisibleForTesting
+ @NonNull
+ ExecutionStats getExecutionStatsLocked(final int userId, @NonNull final String packageName,
+ final int standbyBucket) {
+ if (standbyBucket == NEVER_INDEX) {
+ Slog.wtf(TAG, "getExecutionStatsLocked called for a NEVER app.");
+ return new ExecutionStats();
+ }
+ ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName);
+ if (appStats == null) {
+ appStats = new ExecutionStats[mBucketPeriodsMs.length];
+ mExecutionStatsCache.add(userId, packageName, appStats);
+ }
+ ExecutionStats stats = appStats[standbyBucket];
+ if (stats == null) {
+ stats = new ExecutionStats();
+ appStats[standbyBucket] = stats;
+ }
final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket];
- final long trailingRunDurationMs = getTrailingExecutionTimeLocked(
- userId, packageName, bucketWindowSizeMs);
- return mAllowedTimePerPeriodMs - trailingRunDurationMs;
+ Timer timer = mPkgTimers.get(userId, packageName);
+ if ((timer != null && timer.isActive())
+ || stats.invalidTimeElapsed <= sElapsedRealtimeClock.millis()
+ || stats.windowSizeMs != bucketWindowSizeMs) {
+ // The stats are no longer valid.
+ stats.windowSizeMs = bucketWindowSizeMs;
+ updateExecutionStatsLocked(userId, packageName, stats);
+ }
+
+ return stats;
}
- /** Returns how long the uid has had jobs running within the most recent window. */
@VisibleForTesting
- long getTrailingExecutionTimeLocked(final int userId, @NonNull final String packageName,
- final long windowSizeMs) {
- long totalTime = 0;
+ void updateExecutionStatsLocked(final int userId, @NonNull final String packageName,
+ @NonNull ExecutionStats stats) {
+ stats.executionTimeInWindowMs = 0;
+ stats.bgJobCountInWindow = 0;
+ stats.executionTimeInMaxPeriodMs = 0;
+ stats.bgJobCountInMaxPeriod = 0;
+ stats.quotaCutoffTimeElapsed = 0;
Timer timer = mPkgTimers.get(userId, packageName);
final long nowElapsed = sElapsedRealtimeClock.millis();
+ stats.invalidTimeElapsed = nowElapsed + MAX_PERIOD_MS;
if (timer != null && timer.isActive()) {
- totalTime = timer.getCurrentDuration(nowElapsed);
+ stats.executionTimeInWindowMs =
+ stats.executionTimeInMaxPeriodMs = timer.getCurrentDuration(nowElapsed);
+ stats.bgJobCountInWindow = stats.bgJobCountInMaxPeriod = timer.getBgJobCount();
+ // If the timer is active, the value will be stale at the next method call, so
+ // invalidate now.
+ stats.invalidTimeElapsed = nowElapsed;
+ if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) {
+ stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
+ nowElapsed - mAllowedTimeIntoQuotaMs);
+ }
+ if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) {
+ stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
+ nowElapsed - mMaxExecutionTimeIntoQuotaMs);
+ }
}
List<TimingSession> sessions = mTimingSessions.get(userId, packageName);
if (sessions == null || sessions.size() == 0) {
- return totalTime;
+ return;
}
- final long startElapsed = nowElapsed - windowSizeMs;
+ final long startWindowElapsed = nowElapsed - stats.windowSizeMs;
+ final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS;
+ // The minimum time between the start time and the beginning of the sessions that were
+ // looked at --> how much time the stats will be valid for.
+ long emptyTimeMs = Long.MAX_VALUE;
// Sessions are non-overlapping and in order of occurrence, so iterating backwards will get
// the most recent ones.
for (int i = sessions.size() - 1; i >= 0; --i) {
TimingSession session = sessions.get(i);
- if (startElapsed < session.startTimeElapsed) {
- totalTime += session.endTimeElapsed - session.startTimeElapsed;
- } else if (startElapsed < session.endTimeElapsed) {
+
+ // Window management.
+ if (startWindowElapsed < session.startTimeElapsed) {
+ stats.executionTimeInWindowMs += session.endTimeElapsed - session.startTimeElapsed;
+ stats.bgJobCountInWindow += session.bgJobCount;
+ emptyTimeMs = Math.min(emptyTimeMs, session.startTimeElapsed - startWindowElapsed);
+ if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) {
+ stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
+ session.startTimeElapsed + stats.executionTimeInWindowMs
+ - mAllowedTimeIntoQuotaMs);
+ }
+ } else if (startWindowElapsed < session.endTimeElapsed) {
// The session started before the window but ended within the window. Only include
// the portion that was within the window.
- totalTime += session.endTimeElapsed - startElapsed;
+ stats.executionTimeInWindowMs += session.endTimeElapsed - startWindowElapsed;
+ stats.bgJobCountInWindow += session.bgJobCount;
+ emptyTimeMs = 0;
+ if (stats.executionTimeInWindowMs >= mAllowedTimeIntoQuotaMs) {
+ stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
+ startWindowElapsed + stats.executionTimeInWindowMs
+ - mAllowedTimeIntoQuotaMs);
+ }
+ }
+
+ // Max period check.
+ if (startMaxElapsed < session.startTimeElapsed) {
+ stats.executionTimeInMaxPeriodMs +=
+ session.endTimeElapsed - session.startTimeElapsed;
+ stats.bgJobCountInMaxPeriod += session.bgJobCount;
+ emptyTimeMs = Math.min(emptyTimeMs, session.startTimeElapsed - startMaxElapsed);
+ if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) {
+ stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
+ session.startTimeElapsed + stats.executionTimeInMaxPeriodMs
+ - mMaxExecutionTimeIntoQuotaMs);
+ }
+ } else if (startMaxElapsed < session.endTimeElapsed) {
+ // The session started before the window but ended within the window. Only include
+ // the portion that was within the window.
+ stats.executionTimeInMaxPeriodMs += session.endTimeElapsed - startMaxElapsed;
+ stats.bgJobCountInMaxPeriod += session.bgJobCount;
+ emptyTimeMs = 0;
+ if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeIntoQuotaMs) {
+ stats.quotaCutoffTimeElapsed = Math.max(stats.quotaCutoffTimeElapsed,
+ startMaxElapsed + stats.executionTimeInMaxPeriodMs
+ - mMaxExecutionTimeIntoQuotaMs);
+ }
} else {
// This session ended before the window. No point in going any further.
- return totalTime;
+ break;
+ }
+ }
+ stats.invalidTimeElapsed = nowElapsed + emptyTimeMs;
+ }
+
+ private void invalidateAllExecutionStatsLocked(final int userId,
+ @NonNull final String packageName) {
+ ExecutionStats[] appStats = mExecutionStatsCache.get(userId, packageName);
+ if (appStats != null) {
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+ for (int i = 0; i < appStats.length; ++i) {
+ ExecutionStats stats = appStats[i];
+ if (stats != null) {
+ stats.invalidTimeElapsed = nowElapsed;
+ }
}
}
- return totalTime;
}
@VisibleForTesting
@@ -524,6 +735,8 @@ public final class QuotaController extends StateController {
mTimingSessions.add(userId, packageName, sessions);
}
sessions.add(session);
+ // Adding a new session means that the current stats are now incorrect.
+ invalidateAllExecutionStatsLocked(userId, packageName);
maybeScheduleCleanupAlarmLocked();
}
@@ -657,87 +870,43 @@ public final class QuotaController extends StateController {
@VisibleForTesting
void maybeScheduleStartAlarmLocked(final int userId, @NonNull final String packageName,
final int standbyBucket) {
- final String pkgString = string(userId, packageName);
if (standbyBucket == NEVER_INDEX) {
return;
- } else if (standbyBucket == ACTIVE_INDEX) {
- // ACTIVE apps are "always" in quota.
+ }
+
+ final String pkgString = string(userId, packageName);
+ ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
+ QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
+ if (stats.executionTimeInWindowMs < mAllowedTimePerPeriodMs
+ && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs) {
+ // Already in quota. Why was this method called?
if (DEBUG) {
- Slog.w(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString
- + " even though it is active");
+ Slog.e(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString
+ + " even though it already has "
+ + getRemainingExecutionTimeLocked(userId, packageName, standbyBucket)
+ + "ms in its quota.");
}
- mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget();
-
- QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
if (alarmListener != null) {
// Cancel any pending alarm.
mAlarmManager.cancel(alarmListener);
// Set the trigger time to 0 so that the alarm doesn't think it's still waiting.
alarmListener.setTriggerTime(0);
}
- return;
- }
-
- List<TimingSession> sessions = mTimingSessions.get(userId, packageName);
- if (sessions == null || sessions.size() == 0) {
- // If there are no sessions, then the job is probably in quota.
- if (DEBUG) {
- Slog.wtf(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString
- + " even though it is likely within its quota.");
- }
mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget();
return;
}
-
- final long bucketWindowSizeMs = mBucketPeriodsMs[standbyBucket];
- final long nowElapsed = sElapsedRealtimeClock.millis();
- // How far back we need to look.
- final long startElapsed = nowElapsed - bucketWindowSizeMs;
-
- long totalTime = 0;
- long cutoffTimeElapsed = nowElapsed;
- for (int i = sessions.size() - 1; i >= 0; i--) {
- TimingSession session = sessions.get(i);
- if (startElapsed < session.startTimeElapsed) {
- cutoffTimeElapsed = session.startTimeElapsed;
- totalTime += session.endTimeElapsed - session.startTimeElapsed;
- } else if (startElapsed < session.endTimeElapsed) {
- // The session started before the window but ended within the window. Only
- // include the portion that was within the window.
- cutoffTimeElapsed = startElapsed;
- totalTime += session.endTimeElapsed - startElapsed;
- } else {
- // This session ended before the window. No point in going any further.
- break;
- }
- if (totalTime >= mAllowedTimePerPeriodMs) {
- break;
- }
- }
- if (totalTime < mAllowedTimePerPeriodMs) {
- // Already in quota. Why was this method called?
- if (DEBUG) {
- Slog.w(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString
- + " even though it already has " + (mAllowedTimePerPeriodMs - totalTime)
- + "ms in its quota.");
- }
- mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget();
- return;
- }
-
- QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
if (alarmListener == null) {
alarmListener = new QcAlarmListener(userId, packageName);
mInQuotaAlarmListeners.add(userId, packageName, alarmListener);
}
- // We add all the way back to the beginning of a session (or the window) even when we don't
- // need to (in order to simplify the for loop above), so there might be some extra we
- // need to add back.
- final long extraTimeMs = totalTime - mAllowedTimePerPeriodMs;
// The time this app will have quota again.
- final long inQuotaTimeElapsed =
- cutoffTimeElapsed + extraTimeMs + mQuotaBufferMs + bucketWindowSizeMs;
+ long inQuotaTimeElapsed =
+ stats.quotaCutoffTimeElapsed + stats.windowSizeMs;
+ if (stats.executionTimeInMaxPeriodMs >= mMaxExecutionTimeMs) {
+ inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed,
+ stats.quotaCutoffTimeElapsed + MAX_PERIOD_MS);
+ }
// Only schedule the alarm if:
// 1. There isn't one currently scheduled
// 2. The new alarm is significantly earlier than the previous alarm (which could be the
@@ -747,13 +916,15 @@ public final class QuotaController extends StateController {
// TODO: this might be overengineering. Simplify if proven safe.
if (!alarmListener.isWaiting()
|| inQuotaTimeElapsed < alarmListener.getTriggerTimeElapsed() - 3 * MINUTE_IN_MILLIS
- || alarmListener.getTriggerTimeElapsed() < inQuotaTimeElapsed - mQuotaBufferMs) {
+ || alarmListener.getTriggerTimeElapsed() < inQuotaTimeElapsed) {
if (DEBUG) Slog.d(TAG, "Scheduling start alarm for " + pkgString);
// If the next time this app will have quota is at least 3 minutes before the
// alarm is supposed to go off, reschedule the alarm.
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, inQuotaTimeElapsed,
ALARM_TAG_QUOTA_CHECK, alarmListener, mHandler);
alarmListener.setTriggerTime(inQuotaTimeElapsed);
+ } else if (DEBUG) {
+ Slog.d(TAG, "No need to scheduling start alarm for " + pkgString);
}
}
@@ -816,10 +987,18 @@ public final class QuotaController extends StateController {
// How many background jobs ran during this session.
public final int bgJobCount;
- TimingSession(long startElapsed, long endElapsed, int jobCount) {
+ private final int mHashCode;
+
+ TimingSession(long startElapsed, long endElapsed, int bgJobCount) {
this.startTimeElapsed = startElapsed;
this.endTimeElapsed = endElapsed;
- this.bgJobCount = jobCount;
+ this.bgJobCount = bgJobCount;
+
+ int hashCode = 0;
+ hashCode = 31 * hashCode + hashLong(startTimeElapsed);
+ hashCode = 31 * hashCode + hashLong(endTimeElapsed);
+ hashCode = 31 * hashCode + bgJobCount;
+ mHashCode = hashCode;
}
@Override
@@ -842,7 +1021,7 @@ public final class QuotaController extends StateController {
@Override
public int hashCode() {
- return Arrays.hashCode(new long[] {startTimeElapsed, endTimeElapsed, bgJobCount});
+ return mHashCode;
}
public void dump(IndentingPrintWriter pw) {
@@ -902,6 +1081,9 @@ public final class QuotaController extends StateController {
if (mRunningBgJobs.size() == 1) {
// Started tracking the first job.
mStartTimeElapsed = sElapsedRealtimeClock.millis();
+ // Starting the timer means that all cached execution stats are now
+ // incorrect.
+ invalidateAllExecutionStatsLocked(mPkg.userId, mPkg.packageName);
scheduleCutoff();
}
}
@@ -966,6 +1148,12 @@ public final class QuotaController extends StateController {
}
}
+ int getBgJobCount() {
+ synchronized (mLock) {
+ return mBgJobCount;
+ }
+ }
+
void onChargingChanged(long nowElapsed, boolean isCharging) {
synchronized (mLock) {
if (isCharging) {
@@ -978,6 +1166,9 @@ public final class QuotaController extends StateController {
// repeatedly plugged in and unplugged, the job count for a package may be
// artificially high.
mBgJobCount = mRunningBgJobs.size();
+ // Starting the timer means that all cached execution stats are now
+ // incorrect.
+ invalidateAllExecutionStatsLocked(mPkg.userId, mPkg.packageName);
// Schedule cutoff since we're now actively tracking for quotas again.
scheduleCutoff();
}
@@ -1239,6 +1430,11 @@ public final class QuotaController extends StateController {
}
@VisibleForTesting
+ long getMaxExecutionTimeMs() {
+ return mMaxExecutionTimeMs;
+ }
+
+ @VisibleForTesting
@Nullable
List<TimingSession> getTimingSessions(int userId, String packageName) {
return mTimingSessions.get(userId, packageName);
diff --git a/services/core/java/com/android/server/job/controllers/TimeController.java b/services/core/java/com/android/server/job/controllers/TimeController.java
index 04d579500df9..26f3caf2e487 100644
--- a/services/core/java/com/android/server/job/controllers/TimeController.java
+++ b/services/core/java/com/android/server/job/controllers/TimeController.java
@@ -30,6 +30,7 @@ import android.util.Slog;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateControllerProto;
@@ -68,7 +69,7 @@ public final class TimeController extends StateController {
mNextJobExpiredElapsedMillis = Long.MAX_VALUE;
mNextDelayExpiredElapsedMillis = Long.MAX_VALUE;
- mChainedAttributionEnabled = WorkSource.isChainedBatteryAttributionEnabled(mContext);
+ mChainedAttributionEnabled = mService.isChainedAttributionEnabled();
}
/**
@@ -110,11 +111,24 @@ public final class TimeController extends StateController {
it.next();
}
it.add(job);
+
job.setTrackingController(JobStatus.TRACKING_TIME);
- maybeUpdateAlarmsLocked(
- job.hasTimingDelayConstraint() ? job.getEarliestRunTime() : Long.MAX_VALUE,
- job.hasDeadlineConstraint() ? job.getLatestRunTimeElapsed() : Long.MAX_VALUE,
- deriveWorkSource(job.getSourceUid(), job.getSourcePackageName()));
+ WorkSource ws = deriveWorkSource(job.getSourceUid(), job.getSourcePackageName());
+ final long deadlineExpiredElapsed =
+ job.hasDeadlineConstraint() ? job.getLatestRunTimeElapsed() : Long.MAX_VALUE;
+ final long delayExpiredElapsed =
+ job.hasTimingDelayConstraint() ? job.getEarliestRunTime() : Long.MAX_VALUE;
+ if (mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS) {
+ if (wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_TIMING_DELAY)) {
+ maybeUpdateDelayAlarmLocked(delayExpiredElapsed, ws);
+ }
+ if (wouldBeReadyWithConstraintLocked(job, JobStatus.CONSTRAINT_DEADLINE)) {
+ maybeUpdateDeadlineAlarmLocked(deadlineExpiredElapsed, ws);
+ }
+ } else {
+ maybeUpdateDelayAlarmLocked(delayExpiredElapsed, ws);
+ maybeUpdateDeadlineAlarmLocked(deadlineExpiredElapsed, ws);
+ }
}
}
@@ -133,6 +147,34 @@ public final class TimeController extends StateController {
}
}
+ @Override
+ public void onConstantsUpdatedLocked() {
+ checkExpiredDelaysAndResetAlarm();
+ checkExpiredDeadlinesAndResetAlarm();
+ }
+
+ @Override
+ public void evaluateStateLocked(JobStatus job) {
+ if (!mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS) {
+ return;
+ }
+
+ if (job.hasTimingDelayConstraint()
+ && job.getEarliestRunTime() <= mNextDelayExpiredElapsedMillis) {
+ checkExpiredDelaysAndResetAlarm();
+ }
+ if (job.hasDeadlineConstraint()
+ && job.getLatestRunTimeElapsed() <= mNextJobExpiredElapsedMillis) {
+ checkExpiredDeadlinesAndResetAlarm();
+ }
+ }
+
+ @Override
+ public void reevaluateStateLocked(int uid) {
+ checkExpiredDelaysAndResetAlarm();
+ checkExpiredDeadlinesAndResetAlarm();
+ }
+
/**
* Determines whether this controller can stop tracking the given job.
* The controller is no longer interested in a job once its time constraint is satisfied, and
@@ -156,14 +198,15 @@ public final class TimeController extends StateController {
* Checks list of jobs for ones that have an expired deadline, sending them to the JobScheduler
* if so, removing them from this list, and updating the alarm for the next expiry time.
*/
- private void checkExpiredDeadlinesAndResetAlarm() {
+ @VisibleForTesting
+ void checkExpiredDeadlinesAndResetAlarm() {
synchronized (mLock) {
long nextExpiryTime = Long.MAX_VALUE;
int nextExpiryUid = 0;
String nextExpiryPackageName = null;
final long nowElapsedMillis = sElapsedRealtimeClock.millis();
- Iterator<JobStatus> it = mTrackedJobs.iterator();
+ ListIterator<JobStatus> it = mTrackedJobs.listIterator();
while (it.hasNext()) {
JobStatus job = it.next();
if (!job.hasDeadlineConstraint()) {
@@ -171,9 +214,22 @@ public final class TimeController extends StateController {
}
if (evaluateDeadlineConstraint(job, nowElapsedMillis)) {
- mStateChangedListener.onRunJobNow(job);
+ if (job.isReady()) {
+ // If the job still isn't ready, there's no point trying to rush the
+ // Scheduler.
+ mStateChangedListener.onRunJobNow(job);
+ }
it.remove();
} else { // Sorted by expiry time, so take the next one and stop.
+ if (mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS
+ && !wouldBeReadyWithConstraintLocked(
+ job, JobStatus.CONSTRAINT_DEADLINE)) {
+ if (DEBUG) {
+ Slog.i(TAG,
+ "Skipping " + job + " because deadline won't make it ready.");
+ }
+ continue;
+ }
nextExpiryTime = job.getLatestRunTimeElapsed();
nextExpiryUid = job.getSourceUid();
nextExpiryPackageName = job.getSourcePackageName();
@@ -202,7 +258,8 @@ public final class TimeController extends StateController {
* Handles alarm that notifies us that a job's delay has expired. Iterates through the list of
* tracked jobs and marks them as ready as appropriate.
*/
- private void checkExpiredDelaysAndResetAlarm() {
+ @VisibleForTesting
+ void checkExpiredDelaysAndResetAlarm() {
synchronized (mLock) {
final long nowElapsedMillis = sElapsedRealtimeClock.millis();
long nextDelayTime = Long.MAX_VALUE;
@@ -223,6 +280,15 @@ public final class TimeController extends StateController {
ready = true;
}
} else if (!job.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY)) {
+ if (mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS
+ && !wouldBeReadyWithConstraintLocked(
+ job, JobStatus.CONSTRAINT_TIMING_DELAY)) {
+ if (DEBUG) {
+ Slog.i(TAG,
+ "Skipping " + job + " because delay won't make it ready.");
+ }
+ continue;
+ }
// If this job still doesn't have its delay constraint satisfied,
// then see if it is the next upcoming delay time for the alarm.
final long jobDelayTime = job.getEarliestRunTime();
@@ -262,11 +328,13 @@ public final class TimeController extends StateController {
return false;
}
- private void maybeUpdateAlarmsLocked(long delayExpiredElapsed, long deadlineExpiredElapsed,
- WorkSource ws) {
+ private void maybeUpdateDelayAlarmLocked(long delayExpiredElapsed, WorkSource ws) {
if (delayExpiredElapsed < mNextDelayExpiredElapsedMillis) {
setDelayExpiredAlarmLocked(delayExpiredElapsed, ws);
}
+ }
+
+ private void maybeUpdateDeadlineAlarmLocked(long deadlineExpiredElapsed, WorkSource ws) {
if (deadlineExpiredElapsed < mNextJobExpiredElapsedMillis) {
setDeadlineExpiredAlarmLocked(deadlineExpiredElapsed, ws);
}
@@ -297,11 +365,7 @@ public final class TimeController extends StateController {
}
private long maybeAdjustAlarmTime(long proposedAlarmTimeElapsedMillis) {
- final long earliestWakeupTimeElapsed = sElapsedRealtimeClock.millis();
- if (proposedAlarmTimeElapsedMillis < earliestWakeupTimeElapsed) {
- return earliestWakeupTimeElapsed;
- }
- return proposedAlarmTimeElapsedMillis;
+ return Math.max(proposedAlarmTimeElapsedMillis, sElapsedRealtimeClock.millis());
}
private void updateAlarmWithListenerLocked(String tag, OnAlarmListener listener,
diff --git a/services/core/java/com/android/server/location/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/GnssNetworkConnectivityHandler.java
index b211948f73cf..3903f2a69547 100644
--- a/services/core/java/com/android/server/location/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/GnssNetworkConnectivityHandler.java
@@ -23,7 +23,6 @@ import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkRequest;
-import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
@@ -65,6 +64,10 @@ class GnssNetworkConnectivityHandler {
private static final int APN_IPV6 = 2;
private static final int APN_IPV4V6 = 3;
+ // these must match the NetworkCapability enum flags in IAGnssRil.hal
+ private static final int AGNSS_NET_CAPABILITY_NOT_METERED = 1 << 0;
+ private static final int AGNSS_NET_CAPABILITY_NOT_ROAMING = 1 << 1;
+
// Default time limit in milliseconds for the ConnectivityManager to find a suitable
// network with SUPL connectivity or report an error.
private static final int SUPL_NETWORK_REQUEST_TIMEOUT_MILLIS = 10 * 1000;
@@ -95,23 +98,42 @@ class GnssNetworkConnectivityHandler {
* Network attributes needed when updating HAL about network connectivity status changes.
*/
private static class NetworkAttributes {
- NetworkCapabilities mCapabilities;
- String mApn;
- int mType = ConnectivityManager.TYPE_NONE;
+ private NetworkCapabilities mCapabilities;
+ private String mApn;
+ private int mType = ConnectivityManager.TYPE_NONE;
/**
* Returns true if the capabilities that we pass on to HAL change between {@curCapabilities}
* and {@code newCapabilities}.
*/
- static boolean hasCapabilitiesChanged(NetworkCapabilities curCapabilities,
+ private static boolean hasCapabilitiesChanged(NetworkCapabilities curCapabilities,
NetworkCapabilities newCapabilities) {
if (curCapabilities == null || newCapabilities == null) {
return true;
}
- return curCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
- != newCapabilities.hasCapability(
- NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
+ // Monitor for roaming and metered capability changes.
+ return hasCapabilityChanged(curCapabilities, newCapabilities,
+ NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
+ || hasCapabilityChanged(curCapabilities, newCapabilities,
+ NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+ }
+
+ private static boolean hasCapabilityChanged(NetworkCapabilities curCapabilities,
+ NetworkCapabilities newCapabilities, int capability) {
+ return curCapabilities.hasCapability(capability)
+ != newCapabilities.hasCapability(capability);
+ }
+
+ private static short getCapabilityFlags(NetworkCapabilities capabilities) {
+ short capabilityFlags = 0;
+ if (capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)) {
+ capabilityFlags |= AGNSS_NET_CAPABILITY_NOT_ROAMING;
+ }
+ if (capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)) {
+ capabilityFlags |= AGNSS_NET_CAPABILITY_NOT_METERED;
+ }
+ return capabilityFlags;
}
}
@@ -328,36 +350,31 @@ class GnssNetworkConnectivityHandler {
boolean networkAvailable = isConnected && TelephonyManager.getDefault().getDataEnabled();
NetworkAttributes networkAttributes = updateTrackedNetworksState(isConnected, network,
capabilities);
- String apnName = networkAttributes.mApn;
+ String apn = networkAttributes.mApn;
int type = networkAttributes.mType;
// When isConnected is false, capabilities argument is null. So, use last received
// capabilities.
capabilities = networkAttributes.mCapabilities;
- boolean isRoaming = !capabilities.hasTransport(
- NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
-
Log.i(TAG, String.format(
"updateNetworkState, state=%s, connected=%s, network=%s, capabilities=%s"
- + ", availableNetworkCount: %d",
+ + ", apn: %s, availableNetworkCount: %d",
agpsDataConnStateAsString(),
isConnected,
network,
capabilities,
+ apn,
mAvailableNetworkAttributes.size()));
if (native_is_agps_ril_supported()) {
- String defaultApn = getSelectedApn();
- if (defaultApn == null) {
- defaultApn = "dummy-apn";
- }
-
native_update_network_state(
isConnected,
type,
- isRoaming,
+ !capabilities.hasTransport(
+ NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING), /* isRoaming */
networkAvailable,
- apnName,
- defaultApn);
+ apn != null ? apn : "",
+ network.getNetworkHandle(),
+ NetworkAttributes.getCapabilityFlags(capabilities));
} else if (DEBUG) {
Log.d(TAG, "Skipped network state update because GPS HAL AGPS-RIL is not supported");
}
@@ -398,9 +415,9 @@ class GnssNetworkConnectivityHandler {
// TODO(b/119278134): The synchronous method ConnectivityManager.getNetworkInfo() must
// not be called inside the asynchronous ConnectivityManager.NetworkCallback methods.
NetworkInfo info = mConnMgr.getNetworkInfo(network);
- String apnName = null;
+ String apn = null;
if (info != null) {
- apnName = info.getExtraInfo();
+ apn = info.getExtraInfo();
}
if (DEBUG) {
@@ -413,21 +430,21 @@ class GnssNetworkConnectivityHandler {
}
if (mAGpsDataConnectionState == AGPS_DATA_CONNECTION_OPENING) {
- if (apnName == null) {
+ if (apn == null) {
// assign a dummy value in the case of C2K as otherwise we will have a runtime
// exception in the following call to native_agps_data_conn_open
- apnName = "dummy-apn";
+ apn = "dummy-apn";
}
- int apnIpType = getApnIpType(apnName);
+ int apnIpType = getApnIpType(apn);
setRouting();
if (DEBUG) {
String message = String.format(
"native_agps_data_conn_open: mAgpsApn=%s, mApnIpType=%s",
- apnName,
+ apn,
apnIpType);
Log.d(TAG, message);
}
- native_agps_data_conn_open(apnName, apnIpType);
+ native_agps_data_conn_open(apn, apnIpType);
mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPEN;
}
}
@@ -596,26 +613,6 @@ class GnssNetworkConnectivityHandler {
return APN_INVALID;
}
- private String getSelectedApn() {
- Uri uri = Uri.parse("content://telephony/carriers/preferapn");
- try (Cursor cursor = mContext.getContentResolver().query(
- uri,
- new String[]{"apn"},
- null /* selection */,
- null /* selectionArgs */,
- Carriers.DEFAULT_SORT_ORDER)) {
- if (cursor != null && cursor.moveToFirst()) {
- return cursor.getString(0);
- } else {
- Log.e(TAG, "No APN found to select.");
- }
- } catch (Exception e) {
- Log.e(TAG, "Error encountered on selecting the APN.", e);
- }
-
- return null;
- }
-
// AGPS support
private native void native_agps_data_conn_open(String apn, int apnIpType);
@@ -626,6 +623,6 @@ class GnssNetworkConnectivityHandler {
// AGPS ril support
private static native boolean native_is_agps_ril_supported();
- private native void native_update_network_state(boolean connected, int type,
- boolean roaming, boolean available, String extraInfo, String defaultAPN);
-}
+ private native void native_update_network_state(boolean connected, int type, boolean roaming,
+ boolean available, String apn, long networkHandle, short capabilities);
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index c938f5eec94d..c1f3468627cb 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -20,10 +20,10 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
+import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioManagerInternal;
import android.media.AudioSystem;
-import android.media.MediaDescription;
import android.media.MediaMetadata;
import android.media.Rating;
import android.media.VolumeProvider;
@@ -36,7 +36,6 @@ import android.media.session.MediaController.PlaybackInfo;
import android.media.session.MediaSession;
import android.media.session.ParcelableVolumeInfo;
import android.media.session.PlaybackState;
-import android.media.AudioAttributes;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
@@ -94,8 +93,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
private PendingIntent mLaunchIntent;
// TransportPerformer fields
-
private Bundle mExtras;
+ // Note: Avoid unparceling the bundle inside MediaMetadata since unparceling in system process
+ // may result in throwing an exception.
private MediaMetadata mMetadata;
private PlaybackState mPlaybackState;
private ParceledListSlice mQueue;
@@ -117,6 +117,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
private boolean mIsActive = false;
private boolean mDestroyed = false;
+ private long mDuration;
+ private String mMetadataDescription;
+
public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName,
ISessionCallback cb, String tag, MediaSessionService service, Looper handlerLooper) {
mOwnerPid = ownerPid;
@@ -451,7 +454,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
pw.println(indent + "audioAttrs=" + mAudioAttrs);
pw.println(indent + "volumeType=" + mVolumeType + ", controlType=" + mVolumeControlType
+ ", max=" + mMaxVolume + ", current=" + mCurrentVolume);
- pw.println(indent + "metadata:" + getShortMetadataString());
+ pw.println(indent + "metadata: " + mMetadataDescription);
pw.println(indent + "queueTitle=" + mQueueTitle + ", size="
+ (mQueue == null ? 0 : mQueue.getList().size()));
}
@@ -494,13 +497,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
});
}
- private String getShortMetadataString() {
- int fields = mMetadata == null ? 0 : mMetadata.size();
- MediaDescription description = mMetadata == null ? null : mMetadata
- .getDescription();
- return "size=" + fields + ", description=" + description;
- }
-
private void logCallbackException(
String msg, ISessionControllerCallbackHolder holder, Exception e) {
Log.v(TAG, msg + ", this=" + this + ", callback package=" + holder.mPackageName
@@ -670,12 +666,10 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
private PlaybackState getStateWithUpdatedPosition() {
PlaybackState state;
- long duration = -1;
+ long duration;
synchronized (mLock) {
state = mPlaybackState;
- if (mMetadata != null && mMetadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
- duration = mMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
- }
+ duration = mDuration;
}
PlaybackState result = null;
if (state != null) {
@@ -793,7 +787,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
}
@Override
- public void setMetadata(MediaMetadata metadata) {
+ public void setMetadata(MediaMetadata metadata, long duration, String metadataDescription) {
synchronized (mLock) {
MediaMetadata temp = metadata == null ? null : new MediaMetadata.Builder(metadata)
.build();
@@ -804,6 +798,8 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
temp.size();
}
mMetadata = temp;
+ mDuration = duration;
+ mMetadataDescription = metadataDescription;
}
mHandler.post(MessageHandler.MSG_UPDATE_METADATA);
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 36a027a8fcd0..bad259bafeb8 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -47,17 +47,12 @@ import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
import static android.os.UserHandle.USER_NULL;
import static android.os.UserHandle.USER_SYSTEM;
-import static android.service.notification.NotificationListenerService
- .HINT_HOST_DISABLE_CALL_EFFECTS;
+import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS;
import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
-import static android.service.notification.NotificationListenerService
- .HINT_HOST_DISABLE_NOTIFICATION_EFFECTS;
-import static android.service.notification.NotificationListenerService
- .NOTIFICATION_CHANNEL_OR_GROUP_ADDED;
-import static android.service.notification.NotificationListenerService
- .NOTIFICATION_CHANNEL_OR_GROUP_DELETED;
-import static android.service.notification.NotificationListenerService
- .NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
+import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS;
+import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_ADDED;
+import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED;
+import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
@@ -65,8 +60,7 @@ import static android.service.notification.NotificationListenerService.REASON_CA
import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED;
import static android.service.notification.NotificationListenerService.REASON_CLICK;
import static android.service.notification.NotificationListenerService.REASON_ERROR;
-import static android.service.notification.NotificationListenerService
- .REASON_GROUP_SUMMARY_CANCELED;
+import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL_ALL;
import static android.service.notification.NotificationListenerService.REASON_PACKAGE_BANNED;
@@ -3137,7 +3131,11 @@ public class NotificationManagerService extends SystemService {
throws RemoteException {
Preconditions.checkNotNull(automaticZenRule, "automaticZenRule is null");
Preconditions.checkNotNull(automaticZenRule.getName(), "Name is null");
- Preconditions.checkNotNull(automaticZenRule.getOwner(), "Owner is null");
+ if (automaticZenRule.getOwner() == null
+ && automaticZenRule.getConfigurationActivity() == null) {
+ throw new NullPointerException(
+ "Rule must have a conditionproviderservice and/or configuration activity");
+ }
Preconditions.checkNotNull(automaticZenRule.getConditionId(), "ConditionId is null");
enforcePolicyAccess(Binder.getCallingUid(), "addAutomaticZenRule");
@@ -3150,7 +3148,11 @@ public class NotificationManagerService extends SystemService {
throws RemoteException {
Preconditions.checkNotNull(automaticZenRule, "automaticZenRule is null");
Preconditions.checkNotNull(automaticZenRule.getName(), "Name is null");
- Preconditions.checkNotNull(automaticZenRule.getOwner(), "Owner is null");
+ if (automaticZenRule.getOwner() == null
+ && automaticZenRule.getConfigurationActivity() == null) {
+ throw new NullPointerException(
+ "Rule must have a conditionproviderservice and/or configuration activity");
+ }
Preconditions.checkNotNull(automaticZenRule.getConditionId(), "ConditionId is null");
enforcePolicyAccess(Binder.getCallingUid(), "updateAutomaticZenRule");
@@ -3184,6 +3186,16 @@ public class NotificationManagerService extends SystemService {
}
@Override
+ public void setAutomaticZenRuleState(String id, Condition condition) {
+ Preconditions.checkNotNull(id, "id is null");
+ Preconditions.checkNotNull(condition, "Condition is null");
+
+ enforcePolicyAccess(Binder.getCallingUid(), "setAutomaticZenRuleState");
+
+ mZenModeHelper.setAutomaticZenRuleState(id, condition);
+ }
+
+ @Override
public void setInterruptionFilter(String pkg, int filter) throws RemoteException {
enforcePolicyAccess(pkg, "setInterruptionFilter");
final int zen = NotificationManager.zenModeFromInterruptionFilter(filter, -1);
@@ -6695,7 +6707,7 @@ public class NotificationManagerService extends SystemService {
Bundle hidden = new Bundle();
Bundle systemGeneratedSmartActions = new Bundle();
Bundle smartReplies = new Bundle();
- Bundle audiblyAlerted = new Bundle();
+ Bundle lastAudiblyAlerted = new Bundle();
Bundle noisy = new Bundle();
for (int i = 0; i < N; i++) {
NotificationRecord record = mNotificationList.get(i);
@@ -6727,7 +6739,7 @@ public class NotificationManagerService extends SystemService {
systemGeneratedSmartActions.putParcelableArrayList(key,
record.getSystemGeneratedSmartActions());
smartReplies.putCharSequenceArrayList(key, record.getSmartReplies());
- audiblyAlerted.putBoolean(key, record.getAudiblyAlerted());
+ lastAudiblyAlerted.putLong(key, record.getLastAudiblyAlertedMs());
noisy.putBoolean(key, record.getSound() != null || record.getVibration() != null);
}
final int M = keys.size();
@@ -6740,7 +6752,7 @@ public class NotificationManagerService extends SystemService {
return new NotificationRankingUpdate(keysAr, interceptedKeysAr, visibilityOverrides,
suppressedVisualEffects, importanceAr, explanation, overrideGroupKeys,
channels, overridePeople, snoozeCriteria, showBadge, userSentiment, hidden,
- systemGeneratedSmartActions, smartReplies, audiblyAlerted, noisy);
+ systemGeneratedSmartActions, smartReplies, lastAudiblyAlerted, noisy);
}
boolean hasCompanionDevice(ManagedServiceInfo info) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 50810ccc8cbc..39451d40a97e 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -132,6 +132,9 @@ public final class NotificationRecord {
// user
private long mInterruptionTimeMs;
+ // The most recent time the notification made noise or buzzed the device, or -1 if it did not.
+ private long mLastAudiblyAlertedMs;
+
// Is this record an update of an old record?
public boolean isUpdate;
private int mPackagePriority;
@@ -172,7 +175,6 @@ public final class NotificationRecord {
private final NotificationStats mStats;
private int mUserSentiment;
private boolean mIsInterruptive;
- private boolean mAudiblyAlerted;
private boolean mTextChanged;
private boolean mRecordedInterruption;
private int mNumberOfSmartRepliesAdded;
@@ -1051,7 +1053,7 @@ public final class NotificationRecord {
}
public void setAudiblyAlerted(boolean audiblyAlerted) {
- mAudiblyAlerted = audiblyAlerted;
+ mLastAudiblyAlertedMs = audiblyAlerted ? System.currentTimeMillis() : -1;
}
public void setTextChanged(boolean textChanged) {
@@ -1070,9 +1072,9 @@ public final class NotificationRecord {
return mIsInterruptive;
}
- /** Returns true if the notification audibly alerted the user. */
- public boolean getAudiblyAlerted() {
- return mAudiblyAlerted;
+ /** Returns the time the notification audibly alerted the user. */
+ public long getLastAudiblyAlertedMs() {
+ return mLastAudiblyAlertedMs;
}
protected void setPeopleOverride(ArrayList<String> people) {
diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java
index b080a73c1e42..571f79915484 100644
--- a/services/core/java/com/android/server/notification/ZenModeConditions.java
+++ b/services/core/java/com/android/server/notification/ZenModeConditions.java
@@ -29,8 +29,11 @@ import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
-import java.util.Objects;
+/**
+ * Helper class for managing active rules from
+ * {@link android.service.notification.ConditionProviderService CPSes}.
+ */
public class ZenModeConditions implements ConditionProviders.Callback {
private static final String TAG = ZenModeHelper.TAG;
private static final boolean DEBUG = ZenModeHelper.DEBUG;
@@ -41,8 +44,6 @@ public class ZenModeConditions implements ConditionProviders.Callback {
@VisibleForTesting
protected final ArrayMap<Uri, ComponentName> mSubscriptions = new ArrayMap<>();
- private boolean mFirstEvaluation = true;
-
public ZenModeConditions(ZenModeHelper helper, ConditionProviders conditionProviders) {
mHelper = helper;
mConditionProviders = conditionProviders;
@@ -73,8 +74,10 @@ public class ZenModeConditions implements ConditionProviders.Callback {
final ArraySet<Uri> current = new ArraySet<>();
evaluateRule(config.manualRule, current, null, processSubscriptions);
for (ZenRule automaticRule : config.automaticRules.values()) {
- evaluateRule(automaticRule, current, trigger, processSubscriptions);
- updateSnoozing(automaticRule);
+ if (automaticRule.component != null) {
+ evaluateRule(automaticRule, current, trigger, processSubscriptions);
+ updateSnoozing(automaticRule);
+ }
}
synchronized (mSubscriptions) {
@@ -90,7 +93,6 @@ public class ZenModeConditions implements ConditionProviders.Callback {
}
}
}
- mFirstEvaluation = false;
}
@Override
@@ -114,23 +116,14 @@ public class ZenModeConditions implements ConditionProviders.Callback {
if (DEBUG) Log.d(TAG, "onConditionChanged " + id + " " + condition);
ZenModeConfig config = mHelper.getConfig();
if (config == null) return;
- ComponentName trigger = null;
- boolean updated = updateCondition(id, condition, config.manualRule);
- for (ZenRule automaticRule : config.automaticRules.values()) {
- updated |= updateCondition(id, condition, automaticRule);
- updated |= updateSnoozing(automaticRule);
- if (updated) {
- trigger = automaticRule.component;
- }
- }
- if (updated) {
- mHelper.setConfig(config, trigger, "conditionChanged");
- }
+ mHelper.setAutomaticZenRuleState(id, condition);
}
+ // Only valid for CPS backed rules
private void evaluateRule(ZenRule rule, ArraySet<Uri> current, ComponentName trigger,
boolean processSubscriptions) {
if (rule == null || rule.conditionId == null) return;
+ if (rule.configurationActivity != null) return;
final Uri id = rule.conditionId;
boolean isSystemCondition = false;
for (SystemConditionProviderService sp : mConditionProviders.getSystemProviders()) {
@@ -140,6 +133,7 @@ public class ZenModeConditions implements ConditionProviders.Callback {
isSystemCondition = true;
}
}
+ // ensure that we have a record of the rule if it's backed by an currently alive CPS
if (!isSystemCondition) {
final IConditionProvider cp = mConditionProviders.findConditionProvider(rule.component);
if (DEBUG) Log.d(TAG, "Ensure external rule exists: " + (cp != null) + " for " + id);
@@ -147,7 +141,8 @@ public class ZenModeConditions implements ConditionProviders.Callback {
mConditionProviders.ensureRecordExists(rule.component, id, cp);
}
}
- if (rule.component == null) {
+ // empty rule? disable and bail early
+ if (rule.component == null && rule.enabler == null) {
Log.w(TAG, "No component found for automatic rule: " + rule.conditionId);
rule.enabled = false;
return;
@@ -155,6 +150,8 @@ public class ZenModeConditions implements ConditionProviders.Callback {
if (current != null) {
current.add(id);
}
+
+ // If the rule is bound by a CPS and the CPS is alive, tell them about the rule
if (processSubscriptions && ((trigger != null && trigger.equals(rule.component))
|| isSystemCondition)) {
if (DEBUG) Log.d(TAG, "Subscribing to " + rule.component);
@@ -167,40 +164,20 @@ public class ZenModeConditions implements ConditionProviders.Callback {
if (DEBUG) Log.d(TAG, "zmc failed to subscribe");
}
}
- if (rule.condition == null) {
+ // backfill the rule state from CPS backed components if it's missing
+ if (rule.component != null && rule.condition == null) {
rule.condition = mConditionProviders.findCondition(rule.component, rule.conditionId);
if (rule.condition != null && DEBUG) Log.d(TAG, "Found existing condition for: "
+ rule.conditionId);
}
}
- private boolean isAutomaticActive(ComponentName component) {
- if (component == null) return false;
- final ZenModeConfig config = mHelper.getConfig();
- if (config == null) return false;
- for (ZenRule rule : config.automaticRules.values()) {
- if (component.equals(rule.component) && rule.isAutomaticActive()) {
- return true;
- }
- }
- return false;
- }
-
private boolean updateSnoozing(ZenRule rule) {
- if (rule != null && rule.snoozing && (mFirstEvaluation || !rule.isTrueOrUnknown())) {
+ if (rule != null && rule.snoozing && !rule.isTrueOrUnknown()) {
rule.snoozing = false;
if (DEBUG) Log.d(TAG, "Snoozing reset for " + rule.conditionId);
return true;
}
return false;
}
-
- private boolean updateCondition(Uri id, Condition condition, ZenRule rule) {
- if (id == null || rule == null || rule.conditionId == null) return false;
- if (!rule.conditionId.equals(id)) return false;
- if (Objects.equals(condition, rule.condition)) return false;
- rule.condition = condition;
- return true;
- }
-
}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 94d276c8496d..f01d343948ea 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -26,6 +26,8 @@ import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
@@ -282,20 +284,25 @@ public class ZenModeHelper {
public String addAutomaticZenRule(AutomaticZenRule automaticZenRule, String reason) {
if (!isSystemRule(automaticZenRule)) {
- ServiceInfo owner = getServiceInfo(automaticZenRule.getOwner());
- if (owner == null) {
- throw new IllegalArgumentException("Owner is not a condition provider service");
+ PackageItemInfo component = getServiceInfo(automaticZenRule.getOwner());
+ if (component == null) {
+ component = getActivityInfo(automaticZenRule.getConfigurationActivity());
+ }
+ if (component == null) {
+ throw new IllegalArgumentException("Lacking enabled CPS or config activity");
}
-
int ruleInstanceLimit = -1;
- if (owner.metaData != null) {
- ruleInstanceLimit = owner.metaData.getInt(
+ if (component.metaData != null) {
+ ruleInstanceLimit = component.metaData.getInt(
ConditionProviderService.META_DATA_RULE_INSTANCE_LIMIT, -1);
}
- if (ruleInstanceLimit > 0 && ruleInstanceLimit
- < (getCurrentInstanceCount(automaticZenRule.getOwner()) + 1)) {
+ int newRuleInstanceCount = getCurrentInstanceCount(automaticZenRule.getOwner())
+ + getCurrentInstanceCount(automaticZenRule.getConfigurationActivity())
+ + 1;
+ if (ruleInstanceLimit > 0 && ruleInstanceLimit < newRuleInstanceCount) {
throw new IllegalArgumentException("Rule instance limit exceeded");
}
+
}
ZenModeConfig newConfig;
@@ -377,11 +384,73 @@ public class ZenModeHelper {
}
}
- public int getCurrentInstanceCount(ComponentName owner) {
+ public void setAutomaticZenRuleState(String id, Condition condition) {
+ ZenModeConfig newConfig;
+ synchronized (mConfig) {
+ if (mConfig == null) return;
+
+ newConfig = mConfig.copy();
+ }
+ setAutomaticZenRuleState(newConfig, newConfig.automaticRules.get(id), condition);
+ }
+
+ public void setAutomaticZenRuleState(Uri ruleDefinition, Condition condition) {
+ ZenModeConfig newConfig;
+ synchronized (mConfig) {
+ if (mConfig == null) return;
+ newConfig = mConfig.copy();
+ }
+
+ setAutomaticZenRuleState(newConfig,
+ findMatchingRule(newConfig, ruleDefinition, condition),
+ condition);
+ }
+
+ private void setAutomaticZenRuleState(ZenModeConfig config, ZenRule rule, Condition condition) {
+ if (rule == null) return;
+
+ rule.condition = condition;
+ updateSnoozing(rule);
+ setConfigLocked(config, rule.component, "conditionChanged");
+ }
+
+ private ZenRule findMatchingRule(ZenModeConfig config, Uri id, Condition condition) {
+ if (ruleMatches(id, condition, config.manualRule)) {
+ return config.manualRule;
+ } else {
+ for (ZenRule automaticRule : config.automaticRules.values()) {
+ if (ruleMatches(id, condition, automaticRule)) {
+ return automaticRule;
+ }
+ }
+ }
+ return null;
+ }
+
+ private boolean ruleMatches(Uri id, Condition condition, ZenRule rule) {
+ if (id == null || rule == null || rule.conditionId == null) return false;
+ if (!rule.conditionId.equals(id)) return false;
+ if (Objects.equals(condition, rule.condition)) return false;
+ return true;
+ }
+
+ private boolean updateSnoozing(ZenRule rule) {
+ if (rule != null && rule.snoozing && !rule.isTrueOrUnknown()) {
+ rule.snoozing = false;
+ if (DEBUG) Log.d(TAG, "Snoozing reset for " + rule.conditionId);
+ return true;
+ }
+ return false;
+ }
+
+ public int getCurrentInstanceCount(ComponentName cn) {
+ if (cn == null) {
+ return 0;
+ }
int count = 0;
synchronized (mConfig) {
for (ZenRule rule : mConfig.automaticRules.values()) {
- if (rule.component != null && rule.component.equals(owner)) {
+ if (cn.equals(rule.component) || cn.equals(rule.configurationActivity)) {
count++;
}
}
@@ -401,7 +470,7 @@ public class ZenModeHelper {
if (packages != null) {
final int packageCount = packages.length;
for (int i = 0; i < packageCount; i++) {
- if (packages[i].equals(rule.component.getPackageName())) {
+ if (packages[i].equals(rule.pkg)) {
return true;
}
}
@@ -410,18 +479,6 @@ public class ZenModeHelper {
}
}
- // Checks zen rule properties are the same (doesn't check creation time, name nor enabled)
- // used to check if default rules were customized or not
- private boolean ruleValuesEqual(AutomaticZenRule rule, ZenRule defaultRule) {
- if (rule == null || defaultRule == null) {
- return false;
- }
- return rule.getInterruptionFilter() ==
- NotificationManager.zenModeToInterruptionFilter(defaultRule.zenMode)
- && rule.getConditionId().equals(defaultRule.conditionId)
- && rule.getOwner().equals(defaultRule.component);
- }
-
protected void updateDefaultZenRules() {
updateDefaultAutomaticRuleNames();
for (ZenRule defaultRule : mDefaultConfig.automaticRules.values()) {
@@ -443,7 +500,8 @@ public class ZenModeHelper {
}
private boolean isSystemRule(AutomaticZenRule rule) {
- return ZenModeConfig.SYSTEM_AUTHORITY.equals(rule.getOwner().getPackageName());
+ return rule.getOwner() != null
+ && ZenModeConfig.SYSTEM_AUTHORITY.equals(rule.getOwner().getPackageName());
}
private ServiceInfo getServiceInfo(ComponentName owner) {
@@ -465,11 +523,31 @@ public class ZenModeHelper {
return null;
}
+ private ActivityInfo getActivityInfo(ComponentName configActivity) {
+ Intent queryIntent = new Intent();
+ queryIntent.setComponent(configActivity);
+ List<ResolveInfo> installedComponents = mPm.queryIntentActivitiesAsUser(
+ queryIntent,
+ PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA,
+ UserHandle.getCallingUserId());
+ if (installedComponents != null) {
+ for (int i = 0, count = installedComponents.size(); i < count; i++) {
+ ResolveInfo resolveInfo = installedComponents.get(i);
+ return resolveInfo.activityInfo;
+ }
+ }
+ return null;
+ }
+
private void populateZenRule(AutomaticZenRule automaticZenRule, ZenRule rule, boolean isNew) {
if (isNew) {
rule.id = ZenModeConfig.newRuleId();
rule.creationTime = System.currentTimeMillis();
rule.component = automaticZenRule.getOwner();
+ rule.configurationActivity = automaticZenRule.getConfigurationActivity();
+ rule.pkg = (rule.component != null)
+ ? rule.component.getPackageName()
+ : rule.configurationActivity.getPackageName();
}
if (rule.enabled != automaticZenRule.isEnabled()) {
@@ -488,14 +566,10 @@ public class ZenModeHelper {
}
protected AutomaticZenRule createAutomaticZenRule(ZenRule rule) {
- if (rule.zenPolicy != null) {
- return new AutomaticZenRule(rule.name, rule.component, rule.conditionId, rule.zenPolicy,
- rule.enabled, rule.creationTime);
- } else {
- return new AutomaticZenRule(rule.name, rule.component, rule.conditionId,
- NotificationManager.zenModeToInterruptionFilter(rule.zenMode), rule.enabled,
- rule.creationTime);
- }
+ return new AutomaticZenRule(rule.name, rule.component, rule.configurationActivity,
+ rule.conditionId, rule.zenPolicy,
+ NotificationManager.zenModeToInterruptionFilter(rule.zenMode),
+ rule.enabled, rule.creationTime);
}
public void setManualZenMode(int zenMode, Uri conditionId, String caller, String reason) {
@@ -697,8 +771,9 @@ public class ZenModeHelper {
ZenRule rule = newConfig.automaticRules.get(newConfig.automaticRules.keyAt(i));
if (RULE_INSTANCE_GRACE_PERIOD < (currentTime - rule.creationTime)) {
try {
- mPm.getPackageInfo(rule.component.getPackageName(),
- PackageManager.MATCH_ANY_USER);
+ if (rule.pkg != null) {
+ mPm.getPackageInfo(rule.pkg, PackageManager.MATCH_ANY_USER);
+ }
} catch (PackageManager.NameNotFoundException e) {
newConfig.automaticRules.removeAt(i);
}
@@ -753,11 +828,14 @@ public class ZenModeHelper {
if (DEBUG) Log.d(TAG, "setConfigLocked: store config for user " + config.user);
return true;
}
- // may modify config
+ // handle CPS backed conditions - danger! may modify config
mConditions.evaluateConfig(config, null, false /*processSubscriptions*/);
+
mConfigs.put(config.user, config);
if (DEBUG) Log.d(TAG, "setConfigLocked reason=" + reason, new Throwable());
ZenLog.traceConfig(reason, mConfig, config);
+
+ // send some broadcasts
final boolean policyChanged = !Objects.equals(getNotificationPolicy(mConfig),
getNotificationPolicy(config));
if (!config.equals(mConfig)) {
diff --git a/services/core/java/com/android/server/pm/ModuleInfoProvider.java b/services/core/java/com/android/server/pm/ModuleInfoProvider.java
new file mode 100644
index 000000000000..886cfb25eadd
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ModuleInfoProvider.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.content.Context;
+import android.content.pm.IPackageManager;
+import android.content.pm.ModuleInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.os.Process;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Provides data to back {@code ModuleInfo} related APIs in the package manager. The data is stored
+ * as an XML resource in a configurable "module metadata" package.
+ */
+@VisibleForTesting
+public class ModuleInfoProvider {
+ private static final String TAG = "PackageManager.ModuleInfoProvider";
+
+ /**
+ * The key in the package's application level metadata bundle that provides a resource reference
+ * to the module metadata.
+ */
+ private static final String MODULE_METADATA_KEY = "android.content.pm.MODULE_METADATA";
+
+
+ private final Context mContext;
+ private final IPackageManager mPackageManager;
+ private final Map<String, ModuleInfo> mModuleInfo;
+
+ // TODO: Move this to an earlier boot phase if anybody requires it then.
+ private volatile boolean mMetadataLoaded;
+
+ ModuleInfoProvider(Context context, IPackageManager packageManager) {
+ mContext = context;
+ mPackageManager = packageManager;
+ mModuleInfo = new ArrayMap<>();
+ }
+
+ @VisibleForTesting
+ public ModuleInfoProvider(XmlResourceParser metadata, Resources resources) {
+ mContext = null;
+ mPackageManager = null;
+ mModuleInfo = new ArrayMap<>();
+ loadModuleMetadata(metadata, resources);
+ }
+
+ /** Called by the {@code PackageManager} when it has completed its boot sequence */
+ public void systemReady() {
+ final String packageName = mContext.getResources().getString(
+ R.string.config_defaultModuleMetadataProvider);
+ if (TextUtils.isEmpty(packageName)) {
+ Slog.w(TAG, "No configured module metadata provider.");
+ return;
+ }
+
+ final Resources packageResources;
+ final PackageInfo pi;
+ try {
+ pi = mPackageManager.getPackageInfo(packageName,
+ PackageManager.GET_META_DATA, Process.SYSTEM_UID);
+
+ Context packageContext = mContext.createPackageContext(packageName, 0);
+ packageResources = packageContext.getResources();
+ } catch (RemoteException | NameNotFoundException e) {
+ Slog.w(TAG, "Unable to discover metadata package: " + packageName, e);
+ return;
+ }
+
+ XmlResourceParser parser = packageResources.getXml(
+ pi.applicationInfo.metaData.getInt(MODULE_METADATA_KEY));
+ loadModuleMetadata(parser, packageResources);
+ }
+
+ private void loadModuleMetadata(XmlResourceParser parser, Resources packageResources) {
+ try {
+ // The format for the module metadata is straightforward :
+ //
+ // The following attributes on <module> are currently defined :
+ // -- name : A resource reference to a User visible package name, maps to
+ // ModuleInfo#getName
+ // -- packageName : The package name of the module, see ModuleInfo#getPackageName
+ // -- isHidden : Whether the module is hidden, see ModuleInfo#isHidden
+ //
+ // <module-metadata>
+ // <module name="@string/resource" packageName="package_name" isHidden="false|true" />
+ // <module .... />
+ // </module-metadata>
+
+ XmlUtils.beginDocument(parser, "module-metadata");
+ while (true) {
+ XmlUtils.nextElement(parser);
+ if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
+ break;
+ }
+
+ if (!"module".equals(parser.getName())) {
+ Slog.w(TAG, "Unexpected metadata element: " + parser.getName());
+ mModuleInfo.clear();
+ break;
+ }
+
+ // TODO: The module name here is fetched using the resource configuration applied
+ // at the time of parsing this information. This is probably not the best approach
+ // to dealing with this as we'll now have to listen to all config changes and
+ // regenerate the data if required. Also, is this the right way to parse a resource
+ // reference out of an XML file ?
+ final String moduleName = packageResources.getString(
+ Integer.parseInt(parser.getAttributeValue(null, "name").substring(1)));
+ final String modulePackageName = XmlUtils.readStringAttribute(parser,
+ "packageName");
+ final boolean isHidden = XmlUtils.readBooleanAttribute(parser, "isHidden");
+
+ ModuleInfo mi = new ModuleInfo();
+ mi.setHidden(isHidden);
+ mi.setPackageName(modulePackageName);
+ mi.setName(moduleName);
+
+ mModuleInfo.put(modulePackageName, mi);
+ }
+ } catch (XmlPullParserException | IOException e) {
+ Slog.w(TAG, "Error parsing module metadata", e);
+ mModuleInfo.clear();
+ } finally {
+ parser.close();
+ mMetadataLoaded = true;
+ }
+ }
+
+ List<ModuleInfo> getInstalledModules(int flags) {
+ if (!mMetadataLoaded) {
+ throw new IllegalStateException("Call to getInstalledModules before metadata loaded");
+ }
+
+ return new ArrayList<>(mModuleInfo.values());
+ }
+
+ ModuleInfo getModuleInfo(String packageName, int flags) {
+ if (!mMetadataLoaded) {
+ throw new IllegalStateException("Call to getModuleInfo before metadata loaded");
+ }
+
+ return mModuleInfo.get(packageName);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index a95e73069568..e038f9b606bc 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -165,6 +165,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
// STOPSHIP: This is a temporary mock implementation of staged sessions. This variable
// shouldn't be needed at all.
+ // TODO(b/118865310): Implement staged sessions logic.
@GuardedBy("mStagedSessions")
private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>();
@@ -1130,7 +1131,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
mInstallHandler.post(new Runnable() {
@Override
public void run() {
- // TODO: remove this mock implementation.
+ // TODO(b/118865310): remove this mock implementation.
if (session.isStaged()) {
// If the session is aborted, don't keep it in memory. Only store
// sessions successfully staged.
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6cfb846e3ade..28fb01d6d958 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -162,12 +162,14 @@ import android.content.pm.InstantAppRequest;
import android.content.pm.InstrumentationInfo;
import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.KeySet;
+import android.content.pm.ModuleInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInfoLite;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageList;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.LegacyPackageDeleteObserver;
+import android.content.pm.PackageManager.ModuleInfoFlags;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageManagerInternal.CheckPermissionDelegate;
import android.content.pm.PackageManagerInternal.PackageListObserver;
@@ -323,6 +325,7 @@ import dalvik.system.VMRuntime;
import libcore.io.IoUtils;
import libcore.util.EmptyArray;
+import libcore.util.HexEncoding;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -591,12 +594,6 @@ public class PackageManagerService extends IPackageManager.Stub
public static final int REASON_LAST = REASON_SHARED;
/**
- * Version number for the package parser cache. Increment this whenever the format or
- * extent of cached data changes. See {@code PackageParser#setCacheDir}.
- */
- private static final String PACKAGE_PARSER_CACHE_VERSION = "1";
-
- /**
* Whether the package parser cache is enabled.
*/
private static final boolean DEFAULT_PACKAGE_PARSER_CACHE_ENABLED = true;
@@ -723,6 +720,8 @@ public class PackageManagerService extends IPackageManager.Stub
private PackageManager mPackageManager;
+ private final ModuleInfoProvider mModuleInfoProvider;
+
class PackageParserCallback implements PackageParser.Callback {
@Override public final boolean hasFeature(String feature) {
return PackageManagerService.this.hasSystemFeature(feature, 0);
@@ -1064,9 +1063,6 @@ public class PackageManagerService extends IPackageManager.Stub
+ verificationId + " packageName:" + packageName);
return;
}
- if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
- "Updating IntentFilterVerificationInfo for package " + packageName
- +" verificationId:" + verificationId);
synchronized (mPackages) {
if (verified) {
@@ -1084,19 +1080,47 @@ public class PackageManagerService extends IPackageManager.Stub
int updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
boolean needUpdate = false;
- // We cannot override the STATUS_ALWAYS / STATUS_NEVER states if they have
- // already been set by the User thru the Disambiguation dialog
+ if (DEBUG_DOMAIN_VERIFICATION) {
+ Slog.d(TAG,
+ "Updating IntentFilterVerificationInfo for package " + packageName
+ + " verificationId:" + verificationId
+ + " verified=" + verified);
+ }
+
+ // In a success case, we promote from undefined or ASK to ALWAYS. This
+ // supports a flow where the app fails validation but then ships an updated
+ // APK that passes, and therefore deserves to be in ALWAYS.
+ //
+ // If validation failed, the undefined state winds up in the basic ASK behavior,
+ // but apps that previously passed and became ALWAYS are *demoted* out of
+ // that state, since they would not deserve the ALWAYS behavior in case of a
+ // clean install.
switch (userStatus) {
+ case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS:
+ if (!verified) {
+ // updatedStatus is already UNDEFINED
+ needUpdate = true;
+
+ if (DEBUG_DOMAIN_VERIFICATION) {
+ Slog.d(TAG, "Formerly validated but now failing; demoting");
+ }
+ }
+ break;
+
case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED:
+ // Stay in 'undefined' on verification failure
if (verified) {
updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
- } else {
- updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
}
needUpdate = true;
+ if (DEBUG_DOMAIN_VERIFICATION) {
+ Slog.d(TAG, "Applying update; old=" + userStatus
+ + " new=" + updatedStatus);
+ }
break;
case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK:
+ // Keep in 'ask' on failure
if (verified) {
updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
needUpdate = true;
@@ -1112,6 +1136,8 @@ public class PackageManagerService extends IPackageManager.Stub
packageName, updatedStatus, userId);
scheduleWritePackageRestrictionsLocked(userId);
}
+ } else {
+ Slog.i(TAG, "autoVerify ignored when installing for all users");
}
}
}
@@ -2302,7 +2328,7 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
- mCacheDir = preparePackageParserCache(mIsUpgrade);
+ mCacheDir = preparePackageParserCache();
// Set flag to monitor and not change apk file paths when
// scanning install directories.
@@ -3008,6 +3034,8 @@ public class PackageManagerService extends IPackageManager.Stub
} // synchronized (mPackages)
} // synchronized (mInstallLock)
+ mModuleInfoProvider = new ModuleInfoProvider(mContext, this);
+
// Now after opening every single application zip, make sure they
// are all flushed. Not really needed, but keeps things nice and
// tidy.
@@ -3169,7 +3197,7 @@ public class PackageManagerService extends IPackageManager.Stub
setUpInstantAppInstallerActivityLP(getInstantAppInstallerLPr());
}
- private static File preparePackageParserCache(boolean isUpgrade) {
+ private static @Nullable File preparePackageParserCache() {
if (!DEFAULT_PACKAGE_PARSER_CACHE_ENABLED) {
return null;
}
@@ -3190,17 +3218,25 @@ public class PackageManagerService extends IPackageManager.Stub
return null;
}
- // If this is a system upgrade scenario, delete the contents of the package cache dir.
- // This also serves to "GC" unused entries when the package cache version changes (which
- // can only happen during upgrades).
- if (isUpgrade) {
- FileUtils.deleteContents(cacheBaseDir);
- }
+ // There are several items that need to be combined together to safely
+ // identify cached items. In particular, changing the value of certain
+ // feature flags should cause us to invalidate any caches.
+ final String cacheName = SystemProperties.digestOf(
+ "ro.build.fingerprint",
+ "persist.sys.isolated_storage");
+ // Reconcile cache directories, keeping only what we'd actually use.
+ for (File cacheDir : FileUtils.listFilesOrEmpty(cacheBaseDir)) {
+ if (Objects.equals(cacheName, cacheDir.getName())) {
+ Slog.d(TAG, "Keeping known cache " + cacheDir.getName());
+ } else {
+ Slog.d(TAG, "Destroying unknown cache " + cacheDir.getName());
+ FileUtils.deleteContentsAndDir(cacheDir);
+ }
+ }
- // Return the versioned package cache directory. This is something like
- // "/data/system/package_cache/1"
- File cacheDir = FileUtils.createDir(cacheBaseDir, PACKAGE_PARSER_CACHE_VERSION);
+ // Return the versioned package cache directory.
+ File cacheDir = FileUtils.createDir(cacheBaseDir, cacheName);
if (cacheDir == null) {
// Something went wrong. Attempt to delete everything and return.
@@ -3226,7 +3262,7 @@ public class PackageManagerService extends IPackageManager.Stub
File frameworkDir = new File(Environment.getRootDirectory(), "framework");
if (cacheDir.lastModified() < frameworkDir.lastModified()) {
FileUtils.deleteContents(cacheBaseDir);
- cacheDir = FileUtils.createDir(cacheBaseDir, PACKAGE_PARSER_CACHE_VERSION);
+ cacheDir = FileUtils.createDir(cacheBaseDir, cacheName);
}
}
@@ -4939,6 +4975,16 @@ public class PackageManagerService extends IPackageManager.Stub
}
@Override
+ public ModuleInfo getModuleInfo(String packageName, @ModuleInfoFlags int flags) {
+ return mModuleInfoProvider.getModuleInfo(packageName, flags);
+ }
+
+ @Override
+ public List<ModuleInfo> getInstalledModules(int flags) {
+ return mModuleInfoProvider.getInstalledModules(flags);
+ }
+
+ @Override
public String[] getSystemSharedLibraryNames() {
// allow instant applications
synchronized (mPackages) {
@@ -15269,7 +15315,7 @@ public class PackageManagerService extends IPackageManager.Stub
| (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP);
deletePackageAction = mayDeletePackageLocked(res.removedInfo,
prepareResult.originalPs, prepareResult.disabledPs,
- prepareResult.childPackageSettings, deleteFlags, installArgs.user);
+ prepareResult.childPackageSettings, deleteFlags, null /* all users */);
if (deletePackageAction == null) {
throw new ReconcileFailure(
PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE,
@@ -16708,6 +16754,7 @@ public class PackageManagerService extends IPackageManager.Stub
int status = ivi.getStatus();
switch (status) {
case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED:
+ case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS:
case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK:
return true;
@@ -17356,28 +17403,22 @@ public class PackageManagerService extends IPackageManager.Stub
* make sure this flag is set for partially installed apps. If not its meaningless to
* delete a partially installed application.
*/
- private void removePackageDataLIF(PackageSetting ps, int[] allUserHandles,
+ private void removePackageDataLIF(final PackageSetting deletedPs, int[] allUserHandles,
PackageRemovedInfo outInfo, int flags, boolean writeSettings) {
- String packageName = ps.name;
- if (DEBUG_REMOVE) Slog.d(TAG, "removePackageDataLI: " + ps);
+ String packageName = deletedPs.name;
+ if (DEBUG_REMOVE) Slog.d(TAG, "removePackageDataLI: " + deletedPs);
// Retrieve object to delete permissions for shared user later on
- final PackageParser.Package deletedPkg;
- final PackageSetting deletedPs;
- // reader
- synchronized (mPackages) {
- deletedPkg = mPackages.get(packageName);
- deletedPs = mSettings.mPackages.get(packageName);
- if (outInfo != null) {
- outInfo.removedPackage = packageName;
- outInfo.installerPackageName = ps.installerPackageName;
- outInfo.isStaticSharedLib = deletedPkg != null
- && deletedPkg.staticSharedLibName != null;
- outInfo.populateUsers(deletedPs == null ? null
- : deletedPs.queryInstalledUsers(sUserManager.getUserIds(), true), deletedPs);
- }
+ final PackageParser.Package deletedPkg = deletedPs.pkg;
+ if (outInfo != null) {
+ outInfo.removedPackage = packageName;
+ outInfo.installerPackageName = deletedPs.installerPackageName;
+ outInfo.isStaticSharedLib = deletedPkg != null
+ && deletedPkg.staticSharedLibName != null;
+ outInfo.populateUsers(deletedPs == null ? null
+ : deletedPs.queryInstalledUsers(sUserManager.getUserIds(), true), deletedPs);
}
- removePackageLI(ps.name, (flags & PackageManager.DELETE_CHATTY) != 0);
+ removePackageLI(deletedPs.name, (flags & PackageManager.DELETE_CHATTY) != 0);
if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) {
final PackageParser.Package resolvedPkg;
@@ -17386,8 +17427,8 @@ public class PackageManagerService extends IPackageManager.Stub
} else {
// We don't have a parsed package when it lives on an ejected
// adopted storage device, so fake something together
- resolvedPkg = new PackageParser.Package(ps.name);
- resolvedPkg.setVolumeUuid(ps.volumeUuid);
+ resolvedPkg = new PackageParser.Package(deletedPs.name);
+ resolvedPkg.setVolumeUuid(deletedPs.volumeUuid);
}
destroyAppDataLIF(resolvedPkg, UserHandle.USER_ALL,
StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
@@ -17447,10 +17488,10 @@ public class PackageManagerService extends IPackageManager.Stub
if (DEBUG_REMOVE) {
Slog.d(TAG, " user " + userId + " => " + installed);
}
- if (installed != ps.getInstalled(userId)) {
+ if (installed != deletedPs.getInstalled(userId)) {
installedStateChanged = true;
}
- ps.setInstalled(installed, userId);
+ deletedPs.setInstalled(installed, userId);
}
}
}
@@ -17460,7 +17501,7 @@ public class PackageManagerService extends IPackageManager.Stub
mSettings.writeLPr();
}
if (installedStateChanged) {
- mSettings.writeKernelMappingLPr(ps);
+ mSettings.writeKernelMappingLPr(deletedPs);
}
}
if (removedAppId != -1) {
@@ -17530,12 +17571,12 @@ public class PackageManagerService extends IPackageManager.Stub
/*
* Tries to delete system package.
*/
- private void deleteSystemPackageLIF(DeletePackageAction action,
- PackageParser.Package deletedPkg, PackageSetting deletedPs, int[] allUserHandles,
- int flags, PackageRemovedInfo outInfo, boolean writeSettings)
+ private void deleteSystemPackageLIF(DeletePackageAction action, PackageSetting deletedPs,
+ int[] allUserHandles, int flags, PackageRemovedInfo outInfo, boolean writeSettings)
throws SystemDeleteException {
final boolean applyUserRestrictions
= (allUserHandles != null) && (outInfo.origUsers != null);
+ final PackageParser.Package deletedPkg = deletedPs.pkg;
// Confirm if the system package has been updated
// An updated system app can be deleted. This will also have to restore
// the system pkg from system partition
@@ -17895,16 +17936,18 @@ public class PackageManagerService extends IPackageManager.Stub
PackageSetting[] children = mSettings.getChildSettingsLPr(ps);
action = mayDeletePackageLocked(outInfo, ps, disabledPs, children, flags, user);
}
+ if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageLI: " + packageName + " user " + user);
if (null == action) {
+ if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageLI: action was null");
return false;
}
- if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageLI: " + packageName + " user " + user);
try {
executeDeletePackageLIF(action, packageName, deleteCodeAndResources,
allUserHandles, writeSettings, replacingPackage);
} catch (SystemDeleteException e) {
+ if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageLI: system deletion failure", e);
return false;
}
return true;
@@ -17951,42 +17994,48 @@ public class PackageManagerService extends IPackageManager.Stub
unsuspendForSuspendingPackage(packageName, userId);
}
-
if (!systemApp || action.mayDeleteUnupdatedSystemApp) {
// The caller is asking that the package only be deleted for a single
// user. To do this, we just mark its uninstalled state and delete
// its data. If this is a system app, we only allow this to happen if
// they have set the special DELETE_SYSTEM_APP which requests different
// semantics than normal for uninstalling system apps.
- markPackageUninstalledForUserLPw(ps, user);
-
- if (!systemApp) {
- // Do not uninstall the APK if an app should be cached
- boolean keepUninstalledPackage = shouldKeepUninstalledPackageLPr(packageName);
- if (ps.isAnyInstalled(sUserManager.getUserIds()) || keepUninstalledPackage) {
- // Other user still have this package installed, so all
+ synchronized (mPackages) {
+ markPackageUninstalledForUserLPw(ps, user);
+ if (!systemApp) {
+ // Do not uninstall the APK if an app should be cached
+ boolean keepUninstalledPackage = shouldKeepUninstalledPackageLPr(packageName);
+ if (ps.isAnyInstalled(sUserManager.getUserIds()) || keepUninstalledPackage) {
+ // Other users still have this package installed, so all
+ // we need to do is clear this user's data and save that
+ // it is uninstalled.
+ if (DEBUG_REMOVE) Slog.d(TAG, "Still installed by other users");
+ clearPackageStateForUserLIF(ps, userId, outInfo, flags);
+ scheduleWritePackageRestrictionsLocked(user);
+ return;
+ } else {
+ // We need to set it back to 'installed' so the uninstall
+ // broadcasts will be sent correctly.
+ if (DEBUG_REMOVE) Slog.d(TAG, "Not installed by other users, full delete");
+ if (userId != UserHandle.USER_ALL) {
+ ps.setInstalled(true, userId);
+ } else {
+ for (int origUserId : outInfo.origUsers) {
+ ps.setInstalled(true, origUserId);
+ }
+ }
+ mSettings.writeKernelMappingLPr(ps);
+ }
+ } else {
+ // This is a system app, so we assume that the
+ // other users still have this package installed, so all
// we need to do is clear this user's data and save that
// it is uninstalled.
- if (DEBUG_REMOVE) Slog.d(TAG, "Still installed by other users");
- clearPackageStateForUserLIF(ps, user.getIdentifier(), outInfo, flags);
+ if (DEBUG_REMOVE) Slog.d(TAG, "Deleting system app");
+ clearPackageStateForUserLIF(ps, userId, outInfo, flags);
scheduleWritePackageRestrictionsLocked(user);
return;
- } else {
- // We need to set it back to 'installed' so the uninstall
- // broadcasts will be sent correctly.
- if (DEBUG_REMOVE) Slog.d(TAG, "Not installed by other users, full delete");
- ps.setInstalled(true, user.getIdentifier());
- mSettings.writeKernelMappingLPr(ps);
}
- } else {
- // This is a system app, so we assume that the
- // other users still have this package installed, so all
- // we need to do is clear this user's data and save that
- // it is uninstalled.
- if (DEBUG_REMOVE) Slog.d(TAG, "Deleting system app");
- clearPackageStateForUserLIF(ps, user.getIdentifier(), outInfo, flags);
- scheduleWritePackageRestrictionsLocked(user);
- return;
}
}
@@ -18015,8 +18064,7 @@ public class PackageManagerService extends IPackageManager.Stub
if (DEBUG_REMOVE) Slog.d(TAG, "Removing system package: " + ps.name);
// When an updated system application is deleted we delete the existing resources
// as well and fall back to existing code in system partition
- deleteSystemPackageLIF(
- action, ps.pkg, ps, allUserHandles, flags, outInfo, writeSettings);
+ deleteSystemPackageLIF(action, ps, allUserHandles, flags, outInfo, writeSettings);
} else {
if (DEBUG_REMOVE) Slog.d(TAG, "Removing non-system package: " + ps.name);
deleteInstalledPackageLIF(ps, deleteCodeAndResources, flags, allUserHandles,
@@ -20210,6 +20258,8 @@ public class PackageManagerService extends IPackageManager.Stub
}
}, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
}
+
+ mModuleInfoProvider.systemReady();
}
public void waitForAppDataPrepared() {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index d1d5818b8c46..4f20590a47ff 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -2691,10 +2691,12 @@ public class UserManagerService extends IUserManager.Stub {
if (!isGuest && !isManagedProfile && !isDemo && isUserLimitReached()) {
// If we're not adding a guest/demo user or a managed profile and the limit has
// been reached, cannot add a user.
+ Log.e(LOG_TAG, "Cannot add user. Maximum user limit is reached.");
return null;
}
// If we're adding a guest and there already exists one, bail.
if (isGuest && findCurrentGuestUser() != null) {
+ Log.e(LOG_TAG, "Cannot add guest user. Guest user already exists.");
return null;
}
// In legacy mode, restricted profile's parent can only be the owner user
@@ -2937,13 +2939,26 @@ public class UserManagerService extends IUserManager.Stub {
final UserData userData;
int currentUser = ActivityManager.getCurrentUser();
if (currentUser == userHandle) {
- Log.w(LOG_TAG, "Current user cannot be removed");
+ Log.w(LOG_TAG, "Current user cannot be removed.");
return false;
}
synchronized (mPackagesLock) {
synchronized (mUsersLock) {
userData = mUsers.get(userHandle);
- if (userHandle == 0 || userData == null || mRemovingUserIds.get(userHandle)) {
+ if (userHandle == UserHandle.USER_SYSTEM) {
+ Log.e(LOG_TAG, "System user cannot be removed.");
+ return false;
+ }
+
+ if (userData == null) {
+ Log.e(LOG_TAG, String.format(
+ "Cannot remove user %d, invalid user id provided.", userHandle));
+ return false;
+ }
+
+ if (mRemovingUserIds.get(userHandle)) {
+ Log.e(LOG_TAG, String.format(
+ "User %d is already scheduled for removal.", userHandle));
return false;
}
@@ -2962,7 +2977,7 @@ public class UserManagerService extends IUserManager.Stub {
try {
mAppOpsService.removeUser(userHandle);
} catch (RemoteException e) {
- Log.w(LOG_TAG, "Unable to notify AppOpsService of removing user", e);
+ Log.w(LOG_TAG, "Unable to notify AppOpsService of removing user.", e);
}
if (userData.info.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
@@ -2986,6 +3001,7 @@ public class UserManagerService extends IUserManager.Stub {
}
});
} catch (RemoteException e) {
+ Log.w(LOG_TAG, "Failed to stop user during removal.", e);
return false;
}
return res == ActivityManager.USER_OP_SUCCESS;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 3ba1155d2316..f370edf50708 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -599,6 +599,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
private boolean mAodShowing;
+ private boolean mPerDisplayFocusEnabled = false;
+ private int mTopFocusedDisplayId = INVALID_DISPLAY;
+
private static final int MSG_ENABLE_POINTER_LOCATION = 1;
private static final int MSG_DISABLE_POINTER_LOCATION = 2;
private static final int MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK = 3;
@@ -1811,6 +1814,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mHandleVolumeKeysInWM = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_handleVolumeKeysInWindowManager);
+ mPerDisplayFocusEnabled = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_perDisplayFocusEnabled);
+
readConfigurationDependentBehaviors();
mAccessibilityManager = (AccessibilityManager) context.getSystemService(
@@ -2542,6 +2548,23 @@ public class PhoneWindowManager implements WindowManagerPolicy {
/** {@inheritDoc} */
@Override
public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {
+ final long result = interceptKeyBeforeDispatchingInner(win, event, policyFlags);
+ final int eventDisplayId = event.getDisplayId();
+ if (result == 0 && !mPerDisplayFocusEnabled
+ && eventDisplayId != INVALID_DISPLAY && eventDisplayId != mTopFocusedDisplayId) {
+ // Someone tries to send a key event to a display which doesn't have a focused window.
+ // We drop the event here, or it will cause ANR.
+ // TODO (b/121057974): The user may be confused about why the key doesn't work, so we
+ // may need to deal with this problem.
+ Slog.i(TAG, "Dropping this event targeting display #" + eventDisplayId
+ + " because the focus is on display #" + mTopFocusedDisplayId);
+ return -1;
+ }
+ return result;
+ }
+
+ private long interceptKeyBeforeDispatchingInner(WindowState win, KeyEvent event,
+ int policyFlags) {
final boolean keyguardOn = keyguardOn();
final int keyCode = event.getKeyCode();
final int repeatCount = event.getRepeatCount();
@@ -3123,6 +3146,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
@Override
+ public void setTopFocusedDisplay(int displayId) {
+ mTopFocusedDisplayId = displayId;
+ }
+
+ @Override
public void registerShortcutKey(long shortcutCode, IShortcutService shortcutService)
throws RemoteException {
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 3d474e331fb3..3da325c55b32 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -1034,6 +1034,13 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
public KeyEvent dispatchUnhandledKey(WindowState win, KeyEvent event, int policyFlags);
/**
+ * Called when the top focused display is changed.
+ *
+ * @param displayId The ID of the top focused display.
+ */
+ void setTopFocusedDisplay(int displayId);
+
+ /**
* Apply the keyguard policy to a specific window.
*
* @param win The window to apply the keyguard policy.
diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java
index 35013de6a4eb..f37ca12bbd7f 100644
--- a/services/core/java/com/android/server/role/RoleManagerService.java
+++ b/services/core/java/com/android/server/role/RoleManagerService.java
@@ -503,6 +503,18 @@ public class RoleManagerService extends SystemService implements RoleUserState.C
return userState.removeRoleHolder(roleName, packageName);
}
+ @Override
+ public List<String> getHeldRolesFromController(@NonNull String packageName) {
+ Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
+ getContext().enforceCallingOrSelfPermission(
+ RoleManager.PERMISSION_MANAGE_ROLES_FROM_CONTROLLER,
+ "getRolesHeldFromController");
+
+ int userId = UserHandle.getCallingUserId();
+ RoleUserState userState = getOrCreateUserState(userId);
+ return userState.getHeldRoles(packageName);
+ }
+
@CheckResult
private int handleIncomingUser(@UserIdInt int userId, @NonNull String name) {
return ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,
diff --git a/services/core/java/com/android/server/role/RoleManagerShellCommand.java b/services/core/java/com/android/server/role/RoleManagerShellCommand.java
index 336b311723a9..b245e987022d 100644
--- a/services/core/java/com/android/server/role/RoleManagerShellCommand.java
+++ b/services/core/java/com/android/server/role/RoleManagerShellCommand.java
@@ -23,6 +23,7 @@ import android.app.role.IRoleManagerCallback;
import android.os.RemoteException;
import android.os.ShellCommand;
import android.os.UserHandle;
+import android.util.Log;
import java.io.PrintWriter;
import java.util.concurrent.CompletableFuture;
@@ -47,7 +48,8 @@ class RoleManagerShellCommand extends ShellCommand {
mResult.get(5, TimeUnit.SECONDS);
return 0;
} catch (Exception e) {
- getErrPrintWriter().println("Error: " + e.toString());
+ getErrPrintWriter().println("Error: see logcat for details.\n"
+ + Log.getStackTraceString(e));
return -1;
}
}
diff --git a/services/core/java/com/android/server/role/RoleUserState.java b/services/core/java/com/android/server/role/RoleUserState.java
index d55e261986d0..630a39caeb08 100644
--- a/services/core/java/com/android/server/role/RoleUserState.java
+++ b/services/core/java/com/android/server/role/RoleUserState.java
@@ -47,6 +47,7 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -315,6 +316,21 @@ public class RoleUserState {
}
/**
+ * @see android.app.role.RoleManager#getHeldRolesFromController
+ */
+ @NonNull
+ public List<String> getHeldRoles(@NonNull String packageName) {
+ ArrayList<String> result = new ArrayList<>();
+ int size = mRoles.size();
+ for (int i = 0; i < size; i++) {
+ if (mRoles.valueAt(i).contains(packageName)) {
+ result.add(mRoles.keyAt(i));
+ }
+ }
+ return result;
+ }
+
+ /**
* Schedule writing the state to file.
*/
@GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index f0ebb7512015..4ec8b87be3ac 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -74,6 +74,7 @@ import android.os.StatsDimensionsValue;
import android.os.StatsLogEventWrapper;
import android.os.SynchronousResultReceiver;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.Temperature;
import android.os.UserHandle;
import android.os.UserManager;
@@ -1140,7 +1141,8 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
e.writeLong(rssHighWaterMarkInBytes);
pulledData.add(e);
}
- // TODO(b/119598534): Reset HWM counters here.
+ // Invoke rss_hwm_reset binary to reset RSS HWM counters for all processes.
+ SystemProperties.set("sys.rss_hwm_reset.on", "1");
}
private void pullBinderCallsStats(
diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java
index 10542d52e880..973499f098b2 100644
--- a/services/core/java/com/android/server/wm/ActivityDisplay.java
+++ b/services/core/java/com/android/server/wm/ActivityDisplay.java
@@ -212,7 +212,7 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack>
removeStackReferenceIfNeeded(stack);
releaseSelfIfNeeded();
mService.updateSleepIfNeededLocked();
- onStackOrderChanged();
+ onStackOrderChanged(stack);
}
void positionChildAtTop(ActivityStack stack, boolean includingParents) {
@@ -280,7 +280,7 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack>
if (!wasContained) {
stack.setParent(this);
}
- onStackOrderChanged();
+ onStackOrderChanged(stack);
}
private int getTopInsertPosition(ActivityStack stack, int candidatePosition) {
@@ -1309,9 +1309,13 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack>
mStackOrderChangedCallbacks.remove(listener);
}
- private void onStackOrderChanged() {
+ /**
+ * Notifies of a stack order change
+ * @param stack The stack which triggered the order change
+ */
+ private void onStackOrderChanged(ActivityStack stack) {
for (int i = mStackOrderChangedCallbacks.size() - 1; i >= 0; i--) {
- mStackOrderChangedCallbacks.get(i).onStackOrderChanged();
+ mStackOrderChangedCallbacks.get(i).onStackOrderChanged(stack);
}
}
@@ -1390,6 +1394,6 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack>
* Callback for when the order of the stacks in the display changes.
*/
interface OnStackOrderChangedListener {
- void onStackOrderChanged();
+ void onStackOrderChanged(ActivityStack stack);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 57bfc2979636..36701ea599dc 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -73,6 +73,8 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_USER_
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.ActivityTaskManagerService.ANIMATE;
+import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
+import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY;
import static com.android.server.wm.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT;
import static com.android.server.wm.TaskRecord.REPARENT_MOVE_STACK_TO_FRONT;
@@ -1339,6 +1341,21 @@ class ActivityStarter {
voiceInteractor);
final int preferredWindowingMode = mLaunchParams.mWindowingMode;
+ computeLaunchingTaskFlags();
+
+ computeSourceStack();
+
+ mIntent.setFlags(mLaunchFlags);
+
+ ActivityRecord reusedActivity = getReusableIntentActivity();
+
+ mSupervisor.getLaunchParamsController().calculate(
+ reusedActivity != null ? reusedActivity.getTaskRecord() : mInTask,
+ r.info.windowLayout, r, sourceRecord, options, PHASE_BOUNDS, mLaunchParams);
+ mPreferredDisplayId =
+ mLaunchParams.hasPreferredDisplay() ? mLaunchParams.mPreferredDisplayId
+ : DEFAULT_DISPLAY;
+
// Do not start home activity if it cannot be launched on preferred display. We are not
// doing this in ActivityStackSupervisor#canPlaceEntityOnDisplay because it might
// fallback to launch on other displays.
@@ -1348,14 +1365,6 @@ class ActivityStarter {
return START_CANCELED;
}
- computeLaunchingTaskFlags();
-
- computeSourceStack();
-
- mIntent.setFlags(mLaunchFlags);
-
- ActivityRecord reusedActivity = getReusableIntentActivity();
-
if (reusedActivity != null) {
// When the flags NEW_TASK and CLEAR_TASK are set, then the task gets reused but
// still needs to be a lock task mode violation since the task gets cleared out and
@@ -1651,14 +1660,13 @@ class ActivityStarter {
mLaunchParams.reset();
+ // Preferred display id is the only state we need for now and it could be updated again
+ // after we located a reusable task (which might be resided in another display).
mSupervisor.getLaunchParamsController().calculate(inTask, r.info.windowLayout, r,
- sourceRecord, options, mLaunchParams);
-
- if (mLaunchParams.hasPreferredDisplay()) {
- mPreferredDisplayId = mLaunchParams.mPreferredDisplayId;
- } else {
- mPreferredDisplayId = DEFAULT_DISPLAY;
- }
+ sourceRecord, options, PHASE_DISPLAY, mLaunchParams);
+ mPreferredDisplayId =
+ mLaunchParams.hasPreferredDisplay() ? mLaunchParams.mPreferredDisplayId
+ : DEFAULT_DISPLAY;
mLaunchMode = r.launchMode;
@@ -2502,14 +2510,9 @@ class ActivityStarter {
if (((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) == 0)
|| mPreferredDisplayId != DEFAULT_DISPLAY) {
- // We don't pass in the default display id into the get launch stack call so it can do a
- // full resolution.
- mLaunchParams.mPreferredDisplayId =
- mPreferredDisplayId != DEFAULT_DISPLAY ? mPreferredDisplayId : INVALID_DISPLAY;
final boolean onTop = aOptions == null || !aOptions.getAvoidMoveToFront();
final ActivityStack stack =
mRootActivityContainer.getLaunchStack(r, aOptions, task, onTop, mLaunchParams);
- mLaunchParams.mPreferredDisplayId = mPreferredDisplayId;
return stack;
}
// Otherwise handle adjacent launch.
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 8624bff11a18..8d49bf374baf 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -91,6 +91,7 @@ import android.graphics.GraphicBuffer;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Binder;
+import android.os.Build;
import android.os.Debug;
import android.os.IBinder;
import android.os.RemoteException;
@@ -733,6 +734,17 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
}
boolean windowsAreFocusable() {
+ if (mTargetSdk < Build.VERSION_CODES.Q) {
+ final int pid = mActivityRecord != null
+ ? (mActivityRecord.app != null ? mActivityRecord.app.getPid() : 0) : 0;
+ final AppWindowToken topFocusedAppOfMyProcess =
+ mWmService.mRoot.mTopFocusedAppByProcess.get(pid);
+ if (topFocusedAppOfMyProcess != null && topFocusedAppOfMyProcess != this) {
+ // For the apps below Q, there can be only one app which has the focused window per
+ // process, because legacy apps may not be ready for a multi-focus system.
+ return false;
+ }
+ }
return getWindowConfiguration().canReceiveKeys() || mAlwaysFocusable;
}
diff --git a/services/core/java/com/android/server/wm/BoundsAnimationController.java b/services/core/java/com/android/server/wm/BoundsAnimationController.java
index 731ebb8a6e86..f9980bebca9e 100644
--- a/services/core/java/com/android/server/wm/BoundsAnimationController.java
+++ b/services/core/java/com/android/server/wm/BoundsAnimationController.java
@@ -128,9 +128,10 @@ public class BoundsAnimationController {
mAnimationHandler = animationHandler;
if (animationHandler != null) {
// If an animation handler is provided, then ensure that it runs on the sf vsync tick
- handler.runWithScissors(() -> mChoreographer = Choreographer.getSfInstance(),
- 0 /* timeout */);
- animationHandler.setProvider(new SfVsyncFrameCallbackProvider(mChoreographer));
+ handler.post(() -> {
+ mChoreographer = Choreographer.getSfInstance();
+ animationHandler.setProvider(new SfVsyncFrameCallbackProvider(mChoreographer));
+ });
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 2f4c5cab2559..1943efca8ad0 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -61,9 +61,9 @@ import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN;
import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
-
import static android.view.WindowManager.TRANSIT_TASK_OPEN;
import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT;
+
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
@@ -103,6 +103,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.CUSTOM_SCREEN_ROTATION;
+import static com.android.server.wm.WindowManagerService.H.REPORT_FOCUS_CHANGE;
import static com.android.server.wm.WindowManagerService.H.REPORT_HARD_KEYBOARD_STATUS_CHANGE;
import static com.android.server.wm.WindowManagerService.H.REPORT_LOSING_FOCUS;
import static com.android.server.wm.WindowManagerService.H.SEND_NEW_CONFIGURATION;
@@ -169,6 +170,7 @@ import android.view.WindowManagerPolicyConstants.PointerEventListener;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ToBooleanFunction;
import com.android.internal.util.function.TriConsumer;
+import com.android.server.AnimationThread;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.wm.utils.DisplayRotationUtil;
import com.android.server.wm.utils.RotationCache;
@@ -861,7 +863,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
AnimationHandler animationHandler = new AnimationHandler();
mBoundsAnimationController = new BoundsAnimationController(service.mContext,
- mAppTransition, SurfaceAnimationThread.getHandler(), animationHandler);
+ mAppTransition, AnimationThread.getHandler(), animationHandler);
if (mWmService.mInputManager != null) {
final InputChannel inputChannel = mWmService.mInputManager.monitorInput("Display "
@@ -2832,6 +2834,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
forAllWindows(mScheduleToastTimeout, false /* traverseTopToBottom */);
}
+ WindowState findFocusedWindowIfNeeded() {
+ return (mWmService.mPerDisplayFocusEnabled
+ || mWmService.mRoot.mTopFocusedAppByProcess.isEmpty()) ? findFocusedWindow() : null;
+ }
+
WindowState findFocusedWindow() {
mTmpWindow = null;
@@ -2844,7 +2851,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
return mTmpWindow;
}
-
/**
* Update the focused window and make some adjustments if the focus has changed.
*
@@ -2856,31 +2862,31 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
* @param updateInputWindows Whether to sync the window information to the input module.
* @return {@code true} if the focused window has changed.
*/
- boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows, boolean focusFound) {
- final WindowState newFocus = findFocusedWindow();
+ boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
+ WindowState newFocus = findFocusedWindowIfNeeded();
if (mCurrentFocus == newFocus) {
return false;
}
boolean imWindowChanged = false;
- // TODO (b/111080190): Multi-Session IME
- if (!focusFound) {
- final WindowState imWindow = mInputMethodWindow;
- if (imWindow != null) {
- final WindowState prevTarget = mInputMethodTarget;
-
- final WindowState newTarget = computeImeTarget(true /* updateImeTarget*/);
- imWindowChanged = prevTarget != newTarget;
-
- if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS
- && mode != UPDATE_FOCUS_WILL_PLACE_SURFACES) {
- assignWindowLayers(false /* setLayoutNeeded */);
- }
+ final WindowState imWindow = mInputMethodWindow;
+ if (imWindow != null) {
+ final WindowState prevTarget = mInputMethodTarget;
+ final WindowState newTarget = computeImeTarget(true /* updateImeTarget*/);
+ imWindowChanged = prevTarget != newTarget;
+
+ if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS
+ && mode != UPDATE_FOCUS_WILL_PLACE_SURFACES) {
+ assignWindowLayers(false /* setLayoutNeeded */);
}
}
if (imWindowChanged) {
mWmService.mWindowsChanged = true;
setLayoutNeeded();
+ newFocus = findFocusedWindowIfNeeded();
+ }
+ if (mCurrentFocus != newFocus) {
+ mWmService.mH.obtainMessage(REPORT_FOCUS_CHANGE, this).sendToTarget();
}
if (DEBUG_FOCUS_LIGHT || mWmService.localLOGV) Slog.v(TAG_WM, "Changing focus from "
diff --git a/services/core/java/com/android/server/wm/LaunchParamsController.java b/services/core/java/com/android/server/wm/LaunchParamsController.java
index 09475777cb6e..59c02f736513 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsController.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsController.java
@@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
+import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_DONE;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP;
@@ -74,7 +75,7 @@ class LaunchParamsController {
* @param result The resulting params.
*/
void calculate(TaskRecord task, WindowLayout layout, ActivityRecord activity,
- ActivityRecord source, ActivityOptions options, LaunchParams result) {
+ ActivityRecord source, ActivityOptions options, int phase, LaunchParams result) {
result.reset();
if (task != null || activity != null) {
@@ -89,7 +90,7 @@ class LaunchParamsController {
mTmpResult.reset();
final LaunchParamsModifier modifier = mModifiers.get(i);
- switch(modifier.onCalculate(task, layout, activity, source, options, mTmpCurrent,
+ switch(modifier.onCalculate(task, layout, activity, source, options, phase, mTmpCurrent,
mTmpResult)) {
case RESULT_SKIP:
// Do not apply any results when we are told to skip
@@ -125,7 +126,7 @@ class LaunchParamsController {
boolean layoutTask(TaskRecord task, WindowLayout layout, ActivityRecord activity,
ActivityRecord source, ActivityOptions options) {
- calculate(task, layout, activity, source, options, mTmpParams);
+ calculate(task, layout, activity, source, options, PHASE_BOUNDS, mTmpParams);
// No changes, return.
if (mTmpParams.isEmpty()) {
@@ -259,6 +260,25 @@ class LaunchParamsController {
*/
int RESULT_CONTINUE = 2;
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({PHASE_DISPLAY, PHASE_WINDOWING_MODE, PHASE_BOUNDS})
+ @interface Phase {}
+
+ /**
+ * Stops once we are done with preferred display calculation.
+ */
+ int PHASE_DISPLAY = 0;
+
+ /**
+ * Stops once we are done with windowing mode calculation.
+ */
+ int PHASE_WINDOWING_MODE = 1;
+
+ /**
+ * Stops once we are done with window bounds calculation.
+ */
+ int PHASE_BOUNDS = 2;
+
/**
* Returns the launch params that the provided activity launch params should be overridden
* to. {@link LaunchParamsModifier} can use this for various purposes, including: 1)
@@ -277,6 +297,7 @@ class LaunchParamsController {
* launched should have this be non-null.
* @param source the Activity that launched a new task. Could be {@code null}.
* @param options {@link ActivityOptions} used to start the activity with.
+ * @param phase the calculation phase, see {@link LaunchParamsModifier.Phase}
* @param currentParams launching params after the process of last {@link
* LaunchParamsModifier}.
* @param outParams the result params to be set.
@@ -284,7 +305,7 @@ class LaunchParamsController {
*/
@Result
int onCalculate(TaskRecord task, WindowLayout layout, ActivityRecord activity,
- ActivityRecord source, ActivityOptions options, LaunchParams currentParams,
- LaunchParams outParams);
+ ActivityRecord source, ActivityOptions options, @Phase int phase,
+ LaunchParams currentParams, LaunchParams outParams);
}
}
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index 42cd8e864322..ec2d6737ee51 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -387,7 +387,13 @@ class RecentsAnimation implements RecentsAnimationCallbacks,
}
@Override
- public void onStackOrderChanged() {
+ public void onStackOrderChanged(ActivityStack stack) {
+ if (DEBUG) Slog.d(TAG, "onStackOrderChanged(): stack=" + stack);
+ if (mDefaultDisplay.getIndexOf(stack) == -1 || !stack.shouldBeVisible(null)) {
+ // The stack is not visible, so ignore this change
+ return;
+ }
+
// If the activity display stack order changes, cancel any running recents animation in
// place
mWindowManager.cancelRecentsAnimationSynchronously(REORDER_KEEP_IN_PLACE,
@@ -429,7 +435,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks,
}
for (int i = targetStack.getChildCount() - 1; i >= 0; i--) {
- final TaskRecord task = (TaskRecord) targetStack.getChildAt(i);
+ final TaskRecord task = targetStack.getChildAt(i);
if (task.getBaseIntent().getComponent().equals(component)) {
return task.getTopActivity();
}
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index 84a32fc44c53..d0144fdf670a 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -1615,33 +1615,35 @@ class RootActivityContainer extends ConfigurationContainer
return candidateTask.getStack();
}
+ int windowingMode;
+ if (launchParams != null) {
+ // When launch params is not null, we always defer to its windowing mode. Sometimes
+ // it could be unspecified, which indicates it should inherit windowing mode from
+ // display.
+ windowingMode = launchParams.mWindowingMode;
+ } else {
+ windowingMode = options != null ? options.getLaunchWindowingMode()
+ : r.getWindowingMode();
+ }
+ windowingMode = activityDisplay.validateWindowingMode(windowingMode, r, candidateTask,
+ r.getActivityType());
+
// Return the topmost valid stack on the display.
for (int i = activityDisplay.getChildCount() - 1; i >= 0; --i) {
final ActivityStack stack = activityDisplay.getChildAt(i);
- if (isValidLaunchStack(stack, r)) {
+ if (isValidLaunchStack(stack, r, windowingMode)) {
return stack;
}
}
// If there is no valid stack on the external display - check if new dynamic stack will do.
if (displayId != DEFAULT_DISPLAY) {
- final int windowingMode;
- if (launchParams != null) {
- // When launch params is not null, we always defer to its windowing mode. Sometimes
- // it could be unspecified, which indicates it should inherit windowing mode from
- // display.
- windowingMode = launchParams.mWindowingMode;
- } else {
- windowingMode = options != null ? options.getLaunchWindowingMode()
- : r.getWindowingMode();
- }
final int activityType =
options != null && options.getLaunchActivityType() != ACTIVITY_TYPE_UNDEFINED
? options.getLaunchActivityType() : r.getActivityType();
return activityDisplay.createStack(windowingMode, activityType, true /*onTop*/);
}
- Slog.w(TAG, "getValidLaunchStackOnDisplay: can't launch on displayId " + displayId);
return null;
}
@@ -1653,7 +1655,7 @@ class RootActivityContainer extends ConfigurationContainer
}
// TODO: Can probably be consolidated into getLaunchStack()...
- private boolean isValidLaunchStack(ActivityStack stack, ActivityRecord r) {
+ private boolean isValidLaunchStack(ActivityStack stack, ActivityRecord r, int windowingMode) {
switch (stack.getActivityType()) {
case ACTIVITY_TYPE_HOME: return r.isActivityTypeHome();
case ACTIVITY_TYPE_RECENTS: return r.isActivityTypeRecents();
@@ -1661,11 +1663,13 @@ class RootActivityContainer extends ConfigurationContainer
}
// There is a 1-to-1 relationship between stack and task when not in
// primary split-windowing mode.
- if (stack.getWindowingMode() != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
- return false;
- } else {
- return r.supportsSplitScreenWindowingMode();
+ if (stack.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+ && r.supportsSplitScreenWindowingMode()
+ && (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+ || windowingMode == WINDOWING_MODE_UNDEFINED)) {
+ return true;
}
+ return false;
}
int resolveActivityType(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 3bbef9248f23..801e5f2038ad 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -79,6 +79,7 @@ import com.android.server.EventLogTags;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.function.Consumer;
/** Root {@link WindowContainer} for the device. */
@@ -122,7 +123,10 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
// The ID of the display which is responsible for receiving display-unspecified key and pointer
// events.
- int mTopFocusedDisplayId = INVALID_DISPLAY;
+ private int mTopFocusedDisplayId = INVALID_DISPLAY;
+
+ // Map from the PID to the top most app which has a focused window of the process.
+ final HashMap<Integer, AppWindowToken> mTopFocusedAppByProcess = new HashMap<>();
// Only a separate transaction until we separate the apply surface changes
// transaction from the global transaction.
@@ -157,50 +161,33 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
}
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
+ mTopFocusedAppByProcess.clear();
boolean changed = false;
int topFocusedDisplayId = INVALID_DISPLAY;
-
for (int i = mChildren.size() - 1; i >= 0; --i) {
final DisplayContent dc = mChildren.get(i);
- changed |= dc.updateFocusedWindowLocked(mode, updateInputWindows,
- topFocusedDisplayId != INVALID_DISPLAY /* focusFound */);
- if (topFocusedDisplayId == INVALID_DISPLAY && dc.mCurrentFocus != null) {
- topFocusedDisplayId = dc.getDisplayId();
+ changed |= dc.updateFocusedWindowLocked(mode, updateInputWindows);
+ final WindowState newFocus = dc.mCurrentFocus;
+ if (newFocus != null) {
+ final int pidOfNewFocus = newFocus.mSession.mPid;
+ if (mTopFocusedAppByProcess.get(pidOfNewFocus) == null) {
+ mTopFocusedAppByProcess.put(pidOfNewFocus, newFocus.mAppToken);
+ }
+ if (topFocusedDisplayId == INVALID_DISPLAY) {
+ topFocusedDisplayId = dc.getDisplayId();
+ }
}
}
if (topFocusedDisplayId == INVALID_DISPLAY) {
topFocusedDisplayId = DEFAULT_DISPLAY;
}
- // TODO(b/118865114): Review if need callback top focus display change to view component.
- // (i.e. Activity or View)
- // Currently we only tracked topFocusedDisplayChanged for notifying InputMethodManager via
- // ViewRootImpl.windowFocusChanged to refocus IME window when top display focus changed
- // but window focus remain the same case.
- // It may need to review if any use case that need to add new callback for reporting
- // this change.
- final boolean topFocusedDisplayChanged =
- mTopFocusedDisplayId != topFocusedDisplayId && mode == UPDATE_FOCUS_NORMAL;
if (mTopFocusedDisplayId != topFocusedDisplayId) {
mTopFocusedDisplayId = topFocusedDisplayId;
- mWmService.mInputManager.setFocusedDisplay(mTopFocusedDisplayId);
+ mWmService.mInputManager.setFocusedDisplay(topFocusedDisplayId);
+ mWmService.mPolicy.setTopFocusedDisplay(topFocusedDisplayId);
if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "New topFocusedDisplayId="
- + mTopFocusedDisplayId);
- }
-
- // Report window focus or top display focus changed through REPORT_FOCUS_CHANGE.
- forAllDisplays((dc) -> {
- final boolean windowFocusChanged =
- dc.mCurrentFocus != null && dc.mCurrentFocus != dc.mLastFocus;
- final boolean isTopFocusedDisplay =
- topFocusedDisplayChanged && dc.getDisplayId() == mTopFocusedDisplayId;
- if (windowFocusChanged || isTopFocusedDisplay) {
- final Message msg = mWmService.mH.obtainMessage(
- WindowManagerService.H.REPORT_FOCUS_CHANGE, dc);
- msg.arg1 = topFocusedDisplayChanged ? 1 : 0;
- mWmService.mH.sendMessage(msg);
- }
- });
-
+ + topFocusedDisplayId);
+ }
return changed;
}
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index 1fb7979fa3e2..5107b522c33b 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -51,6 +51,7 @@ import android.util.Slog;
import android.view.Gravity;
import android.view.View;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.wm.LaunchParamsController.LaunchParams;
import com.android.server.wm.LaunchParamsController.LaunchParamsModifier;
@@ -102,19 +103,27 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {
mSupervisor = supervisor;
}
+ @VisibleForTesting
+ int onCalculate(TaskRecord task, ActivityInfo.WindowLayout layout, ActivityRecord activity,
+ ActivityRecord source, ActivityOptions options, LaunchParams currentParams,
+ LaunchParams outParams) {
+ return onCalculate(task, layout, activity, source, options, PHASE_BOUNDS, currentParams,
+ outParams);
+ }
+
@Override
public int onCalculate(TaskRecord task, ActivityInfo.WindowLayout layout,
ActivityRecord activity, ActivityRecord source, ActivityOptions options,
- LaunchParams currentParams, LaunchParams outParams) {
+ int phase, LaunchParams currentParams, LaunchParams outParams) {
initLogBuilder(task, activity);
- final int result = calculate(task, layout, activity, source, options, currentParams,
+ final int result = calculate(task, layout, activity, source, options, phase, currentParams,
outParams);
outputLog();
return result;
}
private int calculate(TaskRecord task, ActivityInfo.WindowLayout layout,
- ActivityRecord activity, ActivityRecord source, ActivityOptions options,
+ ActivityRecord activity, ActivityRecord source, ActivityOptions options, int phase,
LaunchParams currentParams, LaunchParams outParams) {
final ActivityRecord root;
if (task != null) {
@@ -145,6 +154,10 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {
+ display.getWindowingMode());
}
+ if (phase == PHASE_DISPLAY) {
+ return RESULT_CONTINUE;
+ }
+
// STEP 2: Resolve launch windowing mode.
// STEP 2.1: Determine if any parameter has specified initial bounds. That might be the
// launch bounds from activity options, or size/gravity passed in layout. It also treats the
@@ -247,6 +260,10 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {
outParams.mWindowingMode = launchMode == display.getWindowingMode()
? WINDOWING_MODE_UNDEFINED : launchMode;
+ if (phase == PHASE_WINDOWING_MODE) {
+ return RESULT_CONTINUE;
+ }
+
// STEP 3: Determine final launch bounds based on resolved windowing mode and activity
// requested orientation. We set bounds to empty for fullscreen mode and keep bounds as is
// for all other windowing modes that's not freeform mode. One can read comments in
@@ -288,12 +305,6 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {
displayId = optionLaunchId;
}
- if (displayId == INVALID_DISPLAY && source != null) {
- final int sourceDisplayId = source.getDisplayId();
- if (DEBUG) appendLog("display-from-source=" + sourceDisplayId);
- displayId = sourceDisplayId;
- }
-
ActivityStack stack =
(displayId == INVALID_DISPLAY && task != null) ? task.getStack() : null;
if (stack != null) {
@@ -301,6 +312,12 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {
displayId = stack.mDisplayId;
}
+ if (displayId == INVALID_DISPLAY && source != null) {
+ final int sourceDisplayId = source.getDisplayId();
+ if (DEBUG) appendLog("display-from-source=" + sourceDisplayId);
+ displayId = sourceDisplayId;
+ }
+
if (displayId != INVALID_DISPLAY
&& mSupervisor.mRootActivityContainer.getActivityDisplay(displayId) == null) {
displayId = currentParams.mPreferredDisplayId;
diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
index 53d2cb094c44..c006a7ba44d7 100644
--- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
@@ -16,6 +16,12 @@
package com.android.server.wm;
+import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
+import static android.view.PointerIcon.TYPE_NOT_SPECIFIED;
+import static android.view.PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW;
+import static android.view.PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;
+import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
+
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.input.InputManager;
@@ -25,21 +31,18 @@ import android.view.WindowManagerPolicyConstants.PointerEventListener;
import com.android.server.wm.WindowManagerService.H;
-import static android.view.PointerIcon.TYPE_NOT_SPECIFIED;
-import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
-import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
-import static android.view.PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW;
-import static android.view.PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;
-
public class TaskTapPointerEventListener implements PointerEventListener {
- final private Region mTouchExcludeRegion = new Region();
+ private final Region mTouchExcludeRegion = new Region();
+ private final Region mTmpRegion = new Region();
private final WindowManagerService mService;
private final DisplayContent mDisplayContent;
private final Handler mHandler;
private final Runnable mMoveDisplayToTop;
private final Rect mTmpRect = new Rect();
private int mPointerIconType = TYPE_NOT_SPECIFIED;
+ private int mLastDownX;
+ private int mLastDownY;
public TaskTapPointerEventListener(WindowManagerService service,
DisplayContent displayContent) {
@@ -47,7 +50,22 @@ public class TaskTapPointerEventListener implements PointerEventListener {
mDisplayContent = displayContent;
mHandler = new Handler(mService.mH.getLooper());
mMoveDisplayToTop = () -> {
+ int x;
+ int y;
+ synchronized (this) {
+ x = mLastDownX;
+ y = mLastDownY;
+ }
synchronized (mService.mGlobalLock) {
+ if (!mService.mPerDisplayFocusEnabled
+ && mService.mRoot.getTopFocusedDisplayContent() != mDisplayContent
+ && inputMethodWindowContains(x, y)) {
+ // In a single focus system, if the input method window and the input method
+ // target window are on the different displays, when the user is tapping on the
+ // input method window, we don't move its display to top. Otherwise, the input
+ // method target window will lose the focus.
+ return;
+ }
mDisplayContent.getParent().positionChildAt(WindowContainer.POSITION_TOP,
mDisplayContent, true /* includingParents */);
}
@@ -70,6 +88,8 @@ public class TaskTapPointerEventListener implements PointerEventListener {
mService.mTaskPositioningController.handleTapOutsideTask(
mDisplayContent, x, y);
}
+ mLastDownX = x;
+ mLastDownY = y;
mHandler.post(mMoveDisplayToTop);
}
}
@@ -122,4 +142,13 @@ public class TaskTapPointerEventListener implements PointerEventListener {
private int getDisplayId() {
return mDisplayContent.getDisplayId();
}
+
+ private boolean inputMethodWindowContains(int x, int y) {
+ final WindowState inputMethodWindow = mDisplayContent.mInputMethodWindow;
+ if (inputMethodWindow == null || !inputMethodWindow.isVisibleLw()) {
+ return false;
+ }
+ inputMethodWindow.getTouchableRegion(mTmpRegion);
+ return mTmpRegion.contains(x, y);
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d0521b4f34ed..e3ced8350f0d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -611,6 +611,9 @@ public class WindowManagerService extends IWindowManager.Stub
boolean mClientFreezingScreen = false;
int mAppsFreezingScreen = 0;
+ @VisibleForTesting
+ boolean mPerDisplayFocusEnabled;
+
// State while inside of layoutAndPlaceSurfacesLocked().
boolean mFocusMayChange;
@@ -944,6 +947,8 @@ public class WindowManagerService extends IWindowManager.Stub
com.android.internal.R.integer.config_maxUiWidth);
mDisableTransitionAnimation = context.getResources().getBoolean(
com.android.internal.R.bool.config_disableTransitionAnimation);
+ mPerDisplayFocusEnabled = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_perDisplayFocusEnabled);
mInputManager = inputManager; // Must be before createDisplayContentLocked.
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
mDisplayWindowSettings = new DisplayWindowSettings(this);
@@ -4473,7 +4478,6 @@ public class WindowManagerService extends IWindowManager.Stub
AccessibilityController accessibilityController = null;
- final boolean topFocusedDisplayChanged = msg.arg1 != 0;
synchronized (mGlobalLock) {
// TODO(multidisplay): Accessibility supported only of default desiplay.
if (mAccessibilityController != null && displayContent.isDefaultDisplay) {
@@ -4484,19 +4488,7 @@ public class WindowManagerService extends IWindowManager.Stub
newFocus = displayContent.mCurrentFocus;
}
if (lastFocus == newFocus) {
- // Report focus to ViewRootImpl when top focused display changes.
- // Or, nothing to do for no window focus change.
- if (topFocusedDisplayChanged && newFocus != null) {
- if (DEBUG_FOCUS_LIGHT) {
- Slog.d(TAG, "Reporting focus: " + newFocus
- + " due to top focused display change.");
- }
- // See {@link IWindow#windowFocusChanged} to know why set
- // reportToClient as false.
- newFocus.reportFocusChangedSerialized(true, mInTouchMode,
- false /* reportToClient */);
- notifyFocusChanged();
- }
+ // Focus is not changing, so nothing to do.
return;
}
synchronized (mGlobalLock) {
@@ -4518,15 +4510,13 @@ public class WindowManagerService extends IWindowManager.Stub
if (newFocus != null) {
if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Gaining focus: " + newFocus);
- newFocus.reportFocusChangedSerialized(true, mInTouchMode,
- true /* reportToClient */);
+ newFocus.reportFocusChangedSerialized(true, mInTouchMode);
notifyFocusChanged();
}
if (lastFocus != null) {
if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Losing focus: " + lastFocus);
- lastFocus.reportFocusChangedSerialized(false, mInTouchMode,
- true /* reportToClient */);
+ lastFocus.reportFocusChangedSerialized(false, mInTouchMode);
}
} break;
@@ -4543,8 +4533,7 @@ public class WindowManagerService extends IWindowManager.Stub
for (int i = 0; i < N; i++) {
if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Losing delayed focus: " +
losers.get(i));
- losers.get(i).reportFocusChangedSerialized(false, mInTouchMode,
- true /* reportToClient */);
+ losers.get(i).reportFocusChangedSerialized(false, mInTouchMode);
}
} break;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index d2dfa76d7b34..e78c12cfcc78 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2168,11 +2168,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mTmpRect.inset(-delta, -delta);
}
region.set(mTmpRect);
- region.translate(-mWindowFrames.mFrame.left, -mWindowFrames.mFrame.top);
+ cropRegionToStackBoundsIfNeeded(region);
} else {
// Not modal or full screen modal
- getTouchableRegion(region, true /* forSurface */);
+ getTouchableRegion(region);
}
+ // Translate to surface based coordinates.
+ region.translate(-mWindowFrames.mFrame.left, -mWindowFrames.mFrame.top);
return flags;
}
@@ -2809,16 +2811,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
/** Get the touchable region in global coordinates. */
void getTouchableRegion(Region outRegion) {
- getTouchableRegion(outRegion, false /* forSurface */);
- }
-
- /** If {@param forSuface} is {@code true}, the region will be translated to surface based. */
- private void getTouchableRegion(Region outRegion, boolean forSurface) {
- if (inPinnedWindowingMode() && !isFocused()) {
- outRegion.setEmpty();
- return;
- }
-
final Rect frame = mWindowFrames.mFrame;
switch (mTouchableInsets) {
default:
@@ -2833,18 +2825,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
break;
case TOUCHABLE_INSETS_REGION: {
outRegion.set(mGivenTouchableRegion);
+ outRegion.translate(frame.left, frame.top);
break;
}
}
cropRegionToStackBoundsIfNeeded(outRegion);
-
- if (forSurface) {
- if (mTouchableInsets != TOUCHABLE_INSETS_REGION) {
- outRegion.translate(-frame.left, -frame.top);
- }
- outRegion.getBounds(mTmpRect);
- applyInsets(outRegion, mTmpRect, mAttrs.surfaceInsets);
- }
}
private void cropRegionToStackBoundsIfNeeded(Region region) {
@@ -2866,13 +2851,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
* Report a focus change. Must be called with no locks held, and consistently
* from the same serialized thread (such as dispatched from a handler).
*/
- void reportFocusChangedSerialized(boolean focused, boolean inTouchMode,
- boolean reportToClient) {
+ void reportFocusChangedSerialized(boolean focused, boolean inTouchMode) {
try {
- mClient.windowFocusChanged(focused, inTouchMode, reportToClient);
+ mClient.windowFocusChanged(focused, inTouchMode);
} catch (RemoteException e) {
}
- if (mFocusCallbacks != null && reportToClient) {
+ if (mFocusCallbacks != null) {
final int N = mFocusCallbacks.beginBroadcast();
for (int i=0; i<N; i++) {
IWindowFocusObserver obs = mFocusCallbacks.getBroadcastItem(i);
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 0e349b7893e7..58fd30e225c1 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -92,7 +92,6 @@ using android::hardware::gnss::V1_0::GnssLocationFlags;
using android::hardware::gnss::V1_0::IAGnss;
using android::hardware::gnss::V1_0::IAGnssCallback;
using android::hardware::gnss::V1_0::IAGnssCallback;
-using android::hardware::gnss::V1_0::IAGnssRil;
using android::hardware::gnss::V1_0::IAGnssRilCallback;
using android::hardware::gnss::V1_0::IGnssBatching;
using android::hardware::gnss::V1_0::IGnssBatchingCallback;
@@ -121,6 +120,8 @@ using IGnssMeasurement_V2_0 = android::hardware::gnss::V2_0::IGnssMeasurement;
using IGnssMeasurementCallback_V1_0 = android::hardware::gnss::V1_0::IGnssMeasurementCallback;
using IGnssMeasurementCallback_V1_1 = android::hardware::gnss::V1_1::IGnssMeasurementCallback;
using IGnssMeasurementCallback_V2_0 = android::hardware::gnss::V2_0::IGnssMeasurementCallback;
+using IAGnssRil_V1_0 = android::hardware::gnss::V1_0::IAGnssRil;
+using IAGnssRil_V2_0 = android::hardware::gnss::V2_0::IAGnssRil;
struct GnssDeathRecipient : virtual public hidl_death_recipient
{
@@ -141,7 +142,8 @@ sp<IGnss_V1_0> gnssHal = nullptr;
sp<IGnss_V1_1> gnssHal_V1_1 = nullptr;
sp<IGnss_V2_0> gnssHal_V2_0 = nullptr;
sp<IGnssXtra> gnssXtraIface = nullptr;
-sp<IAGnssRil> agnssRilIface = nullptr;
+sp<IAGnssRil_V1_0> agnssRilIface = nullptr;
+sp<IAGnssRil_V2_0> agnssRilIface_V2_0 = nullptr;
sp<IGnssGeofencing> gnssGeofencingIface = nullptr;
sp<IAGnss> agnssIface = nullptr;
sp<IGnssBatching> gnssBatchingIface = nullptr;
@@ -1247,11 +1249,21 @@ static void android_location_GnssLocationProvider_init_once(JNIEnv* env, jclass
gnssXtraIface = gnssXtra;
}
- auto gnssRil = gnssHal->getExtensionAGnssRil();
- if (!gnssRil.isOk()) {
- ALOGD("Unable to get a handle to AGnssRil");
+ if (gnssHal_V2_0 != nullptr) {
+ auto agnssRil_V2_0 = gnssHal_V2_0->getExtensionAGnssRil_2_0();
+ if (!agnssRil_V2_0.isOk()) {
+ ALOGD("Unable to get a handle to AGnssRil_V2_0");
+ } else {
+ agnssRilIface_V2_0 = agnssRil_V2_0;
+ agnssRilIface = agnssRilIface_V2_0;
+ }
} else {
- agnssRilIface = gnssRil;
+ auto agnssRil_V1_0 = gnssHal->getExtensionAGnssRil();
+ if (!agnssRil_V1_0.isOk()) {
+ ALOGD("Unable to get a handle to AGnssRil");
+ } else {
+ agnssRilIface = agnssRil_V1_0;
+ }
}
auto gnssAgnss = gnssHal->getExtensionAGnss();
@@ -1496,17 +1508,17 @@ static void android_location_GnssLocationProvider_delete_aiding_data(JNIEnv* /*
static void android_location_GnssLocationProvider_agps_set_reference_location_cellid(
JNIEnv* /* env */, jobject /* obj */, jint type, jint mcc, jint mnc, jint lac, jint cid) {
- IAGnssRil::AGnssRefLocation location;
+ IAGnssRil_V1_0::AGnssRefLocation location;
if (agnssRilIface == nullptr) {
ALOGE("No AGPS RIL interface in agps_set_reference_location_cellid");
return;
}
- switch (static_cast<IAGnssRil::AGnssRefLocationType>(type)) {
- case IAGnssRil::AGnssRefLocationType::GSM_CELLID:
- case IAGnssRil::AGnssRefLocationType::UMTS_CELLID:
- location.type = static_cast<IAGnssRil::AGnssRefLocationType>(type);
+ switch (static_cast<IAGnssRil_V1_0::AGnssRefLocationType>(type)) {
+ case IAGnssRil_V1_0::AGnssRefLocationType::GSM_CELLID:
+ case IAGnssRil_V1_0::AGnssRefLocationType::UMTS_CELLID:
+ location.type = static_cast<IAGnssRil_V1_0::AGnssRefLocationType>(type);
location.cellID.mcc = mcc;
location.cellID.mnc = mnc;
location.cellID.lac = lac;
@@ -1529,7 +1541,7 @@ static void android_location_GnssLocationProvider_agps_set_id(JNIEnv *env, jobje
}
const char *setid = env->GetStringUTFChars(setid_string, nullptr);
- agnssRilIface->setSetId((IAGnssRil::SetIDType)type, setid);
+ agnssRilIface->setSetId((IAGnssRil_V1_0::SetIDType)type, setid);
env->ReleaseStringUTFChars(setid_string, setid);
}
@@ -1771,26 +1783,44 @@ static void android_location_GnssNetworkConnectivityHandler_update_network_state
jint type,
jboolean roaming,
jboolean available,
- jstring extraInfo,
- jstring apn) {
- if (agnssRilIface != nullptr) {
+ jstring apn,
+ jlong networkHandle,
+ jshort capabilities) {
+ if (agnssRilIface == nullptr) {
+ ALOGE("AGnssRilInterface does not exist");
+ return;
+ }
+
+ const char *c_apn = env->GetStringUTFChars(apn, nullptr);
+ const android::hardware::hidl_string hidl_apn{c_apn};
+ if (agnssRilIface_V2_0 != nullptr) {
+ IAGnssRil_V2_0::NetworkAttributes networkAttributes = {
+ .networkHandle = static_cast<uint64_t>(networkHandle),
+ .isConnected = static_cast<bool>(connected),
+ .capabilities = static_cast<uint16_t>(capabilities),
+ .apn = hidl_apn
+ };
+
+ auto result = agnssRilIface_V2_0->updateNetworkState_2_0(networkAttributes);
+ if (!result.isOk() || !result) {
+ ALOGE("updateNetworkState_2_0 failed");
+ }
+ } else {
auto result = agnssRilIface->updateNetworkState(connected,
- static_cast<IAGnssRil::NetworkType>(type),
- roaming);
+ static_cast<IAGnssRil_V1_0::NetworkType>(type), roaming);
if (!result.isOk() || !result) {
ALOGE("updateNetworkState failed");
}
- const char *c_apn = env->GetStringUTFChars(apn, nullptr);
- result = agnssRilIface->updateNetworkAvailability(available, c_apn);
- if (!result.isOk() || !result) {
- ALOGE("updateNetworkAvailability failed");
+ if (!hidl_apn.empty()) {
+ result = agnssRilIface->updateNetworkAvailability(available, hidl_apn);
+ if (!result.isOk() || !result) {
+ ALOGE("updateNetworkAvailability failed");
+ }
}
-
- env->ReleaseStringUTFChars(apn, c_apn);
- } else {
- ALOGE("AGnssRilInterface does not exist");
}
+
+ env->ReleaseStringUTFChars(apn, c_apn);
}
static jboolean android_location_GnssGeofenceProvider_is_geofence_supported(
@@ -2098,7 +2128,6 @@ static jboolean android_location_GnssLocationProvider_set_satellite_blacklist(
}
}
-
static jint android_location_GnssBatchingProvider_get_batch_size(JNIEnv*, jclass) {
if (gnssBatchingIface == nullptr) {
return 0; // batching not supported, size = 0
@@ -2317,7 +2346,7 @@ static const JNINativeMethod sNetworkConnectivityMethods[] = {
{"native_is_agps_ril_supported", "()Z",
reinterpret_cast<void *>(android_location_GnssNetworkConnectivityHandler_is_agps_ril_supported)},
{"native_update_network_state",
- "(ZIZZLjava/lang/String;Ljava/lang/String;)V",
+ "(ZIZZLjava/lang/String;JS)V",
reinterpret_cast<void *>(android_location_GnssNetworkConnectivityHandler_update_network_state)},
{"native_agps_data_conn_open",
"(Ljava/lang/String;I)V",
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 7186cdf96dee..bd3dfe96a67e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -164,7 +164,6 @@ import android.net.NetworkUtils;
import android.net.ProxyInfo;
import android.net.Uri;
import android.net.metrics.IpConnectivityLog;
-import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Binder;
import android.os.Build;
@@ -478,7 +477,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
/**
* Strings logged with {@link
- * com.android.internal.logging.nano.MetricsProto.MetricsEvent#PROVISIONING_ENTRY_POINT_ADB}.
+ * com.android.internal.logging.nano.MetricsProto.MetricsEvent#PROVISIONING_ENTRY_POINT_ADB}
+ * and {@link DevicePolicyEnums#PROVISIONING_ENTRY_POINT_ADB}.
*/
private static final String LOG_TAG_PROFILE_OWNER = "profile-owner";
private static final String LOG_TAG_DEVICE_OWNER = "device-owner";
@@ -4328,6 +4328,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
}
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.ADD_CROSS_PROFILE_WIDGET_PROVIDER)
+ .setAdmin(admin)
+ .write();
+
if (changedProviders != null) {
mLocalService.notifyCrossProfileProvidersChanged(userId, changedProviders);
return true;
@@ -4355,6 +4360,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
}
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.REMOVE_CROSS_PROFILE_WIDGET_PROVIDER)
+ .setAdmin(admin)
+ .write();
+
if (changedProviders != null) {
mLocalService.notifyCrossProfileProvidersChanged(userId, changedProviders);
return true;
@@ -5491,6 +5501,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final long id = mInjector.binderClearCallingIdentity();
try {
mCertificateMonitor.uninstallCaCerts(UserHandle.of(userId), aliases);
+ final boolean isDelegate = (admin == null);
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.UNINSTALL_CA_CERTS)
+ .setAdmin(callerPackage)
+ .setBoolean(isDelegate)
+ .write();
} finally {
mInjector.binderRestoreCallingIdentity(id);
}
@@ -5556,7 +5572,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final KeyChainConnection keyChainConnection = KeyChain.bindAsUser(mContext, userHandle);
try {
IKeyChainService keyChain = keyChainConnection.getService();
- return keyChain.removeKeyPair(alias);
+ final boolean result = keyChain.removeKeyPair(alias);
+ final boolean isDelegate = (who == null);
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.REMOVE_KEY_PAIR)
+ .setAdmin(callerPackage)
+ .setBoolean(isDelegate)
+ .write();
+ return result;
} catch (RemoteException e) {
Log.e(LOG_TAG, "Removing keypair", e);
} finally {
@@ -5740,6 +5763,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return false;
}
}
+ final boolean isDelegate = (who == null);
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.GENERATE_KEY_PAIR)
+ .setAdmin(callerPackage)
+ .setBoolean(isDelegate)
+ .setInt(idAttestationFlags)
+ .setStrings(algorithm)
+ .write();
return true;
}
} catch (RemoteException e) {
@@ -5768,6 +5799,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return false;
}
keyChain.setUserSelectable(alias, isUserSelectable);
+ final boolean isDelegate = (who == null);
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_KEY_PAIR_CERTIFICATE)
+ .setAdmin(callerPackage)
+ .setBoolean(isDelegate)
+ .write();
return true;
} catch (InterruptedException e) {
Log.w(LOG_TAG, "Interrupted while setting keypair certificate", e);
@@ -5830,6 +5867,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
sendPrivateKeyAliasResponse(chosenAlias, response);
}
}, null, Activity.RESULT_OK, null, null);
+ final String adminPackageName =
+ (aliasChooser != null ? aliasChooser.getPackageName() : null);
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.CHOOSE_PRIVATE_KEY_ALIAS)
+ .setAdmin(adminPackageName)
+ .write();
} finally {
mInjector.binderRestoreCallingIdentity(id);
}
@@ -5925,9 +5968,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
// If set, remove exclusive scopes from all other delegates
if (exclusiveScopes != null && !exclusiveScopes.isEmpty()) {
- for (Map.Entry<String, List<String>> entry : policy.mDelegationMap.entrySet()) {
- final String currentPackage = entry.getKey();
- final List<String> currentScopes = entry.getValue();
+ for (int i = policy.mDelegationMap.size() - 1; i >= 0; --i) {
+ final String currentPackage = policy.mDelegationMap.keyAt(i);
+ final List<String> currentScopes = policy.mDelegationMap.valueAt(i);
if (!currentPackage.equals(delegatePackage)) {
// Iterate through all other delegates
@@ -5935,7 +5978,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
// And if this delegate had some exclusive scopes which are now moved
// to the new delegate, notify about its delegation changes.
if (currentScopes.isEmpty()) {
- policy.mDelegationMap.remove(currentPackage);
+ policy.mDelegationMap.removeAt(i);
}
sendDelegationChangedBroadcast(currentPackage,
new ArrayList<>(currentScopes), userId);
@@ -6219,6 +6262,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
public void setCertInstallerPackage(ComponentName who, String installerPackage)
throws SecurityException {
setDelegatedScopePreO(who, installerPackage, DELEGATION_CERT_INSTALL);
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_CERT_INSTALLER_PACKAGE)
+ .setAdmin(who)
+ .setStrings(installerPackage)
+ .write();
}
@Override
@@ -6250,6 +6298,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!connectivityManager.setAlwaysOnVpnPackageForUser(userId, vpnPackage, lockdown)) {
throw new UnsupportedOperationException();
}
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_ALWAYS_ON_VPN_PACKAGE)
+ .setAdmin(admin)
+ .setStrings(vpnPackage)
+ .setBoolean(lockdown)
+ .setInt(/* number of vpn packages */ 0)
+ .write();
} finally {
mInjector.binderRestoreCallingIdentity(token);
}
@@ -6965,6 +7020,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
updateScreenCaptureDisabled(userHandle, disabled);
}
}
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_SCREEN_CAPTURE_DISABLED)
+ .setAdmin(who)
+ .setBoolean(disabled)
+ .write();
}
/**
@@ -7036,6 +7096,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
mInjector.binderRestoreCallingIdentity(ident);
}
}
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_AUTO_TIME_REQUIRED)
+ .setAdmin(who)
+ .setBoolean(required)
+ .write();
}
/**
@@ -7167,6 +7232,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
DevicePolicyManager.NOTIFICATION_BUGREPORT_STARTED), UserHandle.ALL);
mHandler.postDelayed(mRemoteBugreportTimeoutRunnable,
RemoteBugreportUtils.REMOTE_BUGREPORT_TIMEOUT_MILLIS);
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.REQUEST_BUGREPORT)
+ .setAdmin(who)
+ .write();
return true;
} catch (RemoteException re) {
// should never happen
@@ -7377,6 +7446,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
// Tell the user manager that the restrictions have changed.
pushUserRestrictions(userHandle);
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_CAMERA_DISABLED)
+ .setAdmin(who)
+ .setBoolean(disabled)
+ .write();
}
/**
@@ -7528,6 +7602,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
// Notify package manager.
mInjector.getPackageManagerInternal().setKeepUninstalledPackages(packageList);
}
+ final boolean isDelegate = (who == null);
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_KEEP_UNINSTALLED_PACKAGES)
+ .setAdmin(callerPackage)
+ .setBoolean(isDelegate)
+ .setStrings(packageList.toArray(new String[0]))
+ .write();
}
@Override
@@ -7585,6 +7666,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (isAdb()) {
// Log device owner provisioning was started using adb.
MetricsLogger.action(mContext, PROVISIONING_ENTRY_POINT_ADB, LOG_TAG_DEVICE_OWNER);
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.PROVISIONING_ENTRY_POINT_ADB)
+ .setAdmin(admin)
+ .setStrings(LOG_TAG_DEVICE_OWNER)
+ .write();
}
mOwners.setDeviceOwner(admin, ownerName, userId);
@@ -7854,6 +7940,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (isAdb()) {
// Log profile owner provisioning was started using adb.
MetricsLogger.action(mContext, PROVISIONING_ENTRY_POINT_ADB, LOG_TAG_PROFILE_OWNER);
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.PROVISIONING_ENTRY_POINT_ADB)
+ .setAdmin(who)
+ .setStrings(LOG_TAG_PROFILE_OWNER)
+ .write();
}
mOwners.setProfileOwner(who, ownerName, userHandle);
@@ -7941,6 +8032,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
mInjector.binderRestoreCallingIdentity(token);
}
}
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_DEVICE_OWNER_LOCK_SCREEN_INFO)
+ .setAdmin(who)
+ .write();
}
@Override
@@ -8127,6 +8222,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final long id = mInjector.binderClearCallingIdentity();
try {
mUserManager.setUserName(userId, profileName);
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_PROFILE_NAME)
+ .setAdmin(who)
+ .write();
} finally {
mInjector.binderRestoreCallingIdentity(id);
}
@@ -8601,6 +8700,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
mInjector.binderRestoreCallingIdentity(id);
}
}
+ final String activityPackage =
+ (activity != null ? activity.getPackageName() : null);
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.ADD_PERSISTENT_PREFERRED_ACTIVITY)
+ .setAdmin(who)
+ .setStrings(activityPackage, getIntentFilterActions(filter))
+ .write();
}
@Override
@@ -8664,6 +8770,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final long id = mInjector.binderClearCallingIdentity();
try {
mUserManager.setApplicationRestrictions(packageName, settings, userHandle);
+ final boolean isDelegate = (who == null);
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_APPLICATION_RESTRICTIONS)
+ .setAdmin(callerPackage)
+ .setBoolean(isDelegate)
+ .setStrings(packageName)
+ .write();
} finally {
mInjector.binderRestoreCallingIdentity(id);
}
@@ -8795,6 +8908,24 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
mInjector.binderRestoreCallingIdentity(id);
}
}
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.ADD_CROSS_PROFILE_INTENT_FILTER)
+ .setAdmin(who)
+ .setStrings(getIntentFilterActions(filter))
+ .setInt(flags)
+ .write();
+ }
+
+ private static String[] getIntentFilterActions(IntentFilter filter) {
+ if (filter == null) {
+ return null;
+ }
+ final int actionsCount = filter.countActions();
+ final String[] actions = new String[actionsCount];
+ for (int i = 0; i < actionsCount; i++) {
+ actions[i] = filter.getAction(i);
+ }
+ return actions;
}
@Override
@@ -8914,6 +9045,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
admin.permittedAccessiblityServices = packageList;
saveSettingsLocked(UserHandle.getCallingUserId());
}
+ final String[] packageArray =
+ packageList != null ? ((List<String>) packageList).toArray(new String[0]) : null;
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_PERMITTED_ACCESSIBILITY_SERVICES)
+ .setAdmin(who)
+ .setStrings(packageArray)
+ .write();
return true;
}
@@ -9088,6 +9226,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
admin.permittedInputMethods = packageList;
saveSettingsLocked(callingUserId);
}
+ final String[] packageArray =
+ packageList != null ? ((List<String>) packageList).toArray(new String[0]) : null;
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_PERMITTED_INPUT_METHODS)
+ .setAdmin(who)
+ .setStrings(packageArray)
+ .write();
return true;
}
@@ -9628,6 +9773,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
public String[] setPackagesSuspended(ComponentName who, String callerPackage,
String[] packageNames, boolean suspended) {
int callingUserId = UserHandle.getCallingUserId();
+ String[] result = null;
synchronized (getLockObject()) {
// Ensure the caller is a DO/PO or a package access delegate.
enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
@@ -9635,7 +9781,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
long id = mInjector.binderClearCallingIdentity();
try {
- return mIPackageManager.setPackagesSuspendedAsUser(packageNames, suspended,
+ result = mIPackageManager
+ .setPackagesSuspendedAsUser(packageNames, suspended,
null, null, null, PLATFORM_PACKAGE_NAME, callingUserId);
} catch (RemoteException re) {
// Shouldn't happen.
@@ -9643,8 +9790,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
} finally {
mInjector.binderRestoreCallingIdentity(id);
}
- return packageNames;
}
+ final boolean isDelegate = (who == null);
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_PACKAGES_SUSPENDED)
+ .setAdmin(callerPackage)
+ .setBoolean(isDelegate)
+ .setStrings(packageNames)
+ .write();
+ if (result != null) {
+ return result;
+ }
+ return packageNames;
}
@Override
@@ -9781,6 +9938,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
public boolean setApplicationHidden(ComponentName who, String callerPackage, String packageName,
boolean hidden) {
int callingUserId = UserHandle.getCallingUserId();
+ boolean result = false;
synchronized (getLockObject()) {
// Ensure the caller is a DO/PO or a package access delegate.
enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
@@ -9788,16 +9946,23 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
long id = mInjector.binderClearCallingIdentity();
try {
- return mIPackageManager.setApplicationHiddenSettingAsUser(
- packageName, hidden, callingUserId);
+ result = mIPackageManager
+ .setApplicationHiddenSettingAsUser(packageName, hidden, callingUserId);
} catch (RemoteException re) {
// shouldn't happen
Slog.e(LOG_TAG, "Failed to setApplicationHiddenSetting", re);
} finally {
mInjector.binderRestoreCallingIdentity(id);
}
- return false;
}
+ final boolean isDelegate = (who == null);
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_APPLICATION_HIDDEN)
+ .setAdmin(callerPackage)
+ .setBoolean(isDelegate)
+ .setStrings(packageName, hidden ? "hidden" : "not_hidden")
+ .write();
+ return result;
}
@Override
@@ -9862,10 +10027,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
mInjector.binderRestoreCallingIdentity(id);
}
}
+ final boolean isDelegate = (who == null);
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.ENABLE_SYSTEM_APP)
+ .setAdmin(callerPackage)
+ .setBoolean(isDelegate)
+ .setStrings(packageName)
+ .write();
}
@Override
public int enableSystemAppWithIntent(ComponentName who, String callerPackage, Intent intent) {
+ int numberOfAppsInstalled = 0;
synchronized (getLockObject()) {
// Ensure the caller is a DO/PO or an enable system app delegate.
enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
@@ -9887,7 +10060,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (VERBOSE_LOG) {
Slog.d(LOG_TAG, "Enabling system activities: " + activitiesToEnable);
}
- int numberOfAppsInstalled = 0;
if (activitiesToEnable != null) {
for (ResolveInfo info : activitiesToEnable) {
if (info.activityInfo != null) {
@@ -9903,7 +10075,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
}
}
- return numberOfAppsInstalled;
} catch (RemoteException e) {
// shouldn't happen
Slog.wtf(LOG_TAG, "Failed to resolve intent for: " + intent);
@@ -9912,6 +10083,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
mInjector.binderRestoreCallingIdentity(id);
}
}
+ final boolean isDelegate = (who == null);
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.ENABLE_SYSTEM_APP_WITH_INTENT)
+ .setAdmin(callerPackage)
+ .setBoolean(isDelegate)
+ .setStrings(intent.getAction())
+ .write();
+ return numberOfAppsInstalled;
}
private boolean isSystemApp(IPackageManager pm, String packageName, int userId)
@@ -9928,6 +10107,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public boolean installExistingPackage(ComponentName who, String callerPackage,
String packageName) {
+ boolean result;
synchronized (getLockObject()) {
// Ensure the caller is a PO or an install existing package delegate
enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
@@ -9946,7 +10126,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
// Install the package.
- return mIPackageManager.installExistingPackageAsUser(packageName, callingUserId,
+ result = mIPackageManager
+ .installExistingPackageAsUser(packageName, callingUserId,
0 /*installFlags*/, PackageManager.INSTALL_REASON_POLICY)
== PackageManager.INSTALL_SUCCEEDED;
} catch (RemoteException re) {
@@ -9956,6 +10137,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
mInjector.binderRestoreCallingIdentity(id);
}
}
+ if (result) {
+ final boolean isDelegate = (who == null);
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.INSTALL_EXISTING_PACKAGE)
+ .setAdmin(callerPackage)
+ .setBoolean(isDelegate)
+ .setStrings(packageName)
+ .write();
+ }
+ return result;
}
@Override
@@ -10019,6 +10210,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
mInjector.binderRestoreCallingIdentity(id);
}
}
+ final boolean isDelegate = (who == null);
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_UNINSTALL_BLOCKED)
+ .setAdmin(callerPackage)
+ .setBoolean(isDelegate)
+ .setStrings(packageName)
+ .write();
}
@Override
@@ -10060,6 +10258,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
saveSettingsLocked(mInjector.userHandleGetCallingUserId());
}
}
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_CROSS_PROFILE_CALLER_ID_DISABLED)
+ .setAdmin(who)
+ .setBoolean(disabled)
+ .write();
}
@Override
@@ -10098,6 +10301,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
saveSettingsLocked(mInjector.userHandleGetCallingUserId());
}
}
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_CROSS_PROFILE_CONTACTS_SEARCH_DISABLED)
+ .setAdmin(who)
+ .setBoolean(disabled)
+ .write();
}
@Override
@@ -10197,6 +10405,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
saveSettingsLocked(UserHandle.getCallingUserId());
}
}
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_BLUETOOTH_CONTACT_SHARING_DISABLED)
+ .setAdmin(who)
+ .setBoolean(disabled)
+ .write();
}
@Override
@@ -10356,6 +10569,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
} else {
sendAdminCommandLocked(admin, DeviceAdminReceiver.ACTION_LOCK_TASK_EXITING);
}
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_LOCKTASK_MODE_ENABLED)
+ .setAdmin(admin.info.getPackageName())
+ .setBoolean(isEnabled)
+ .setStrings(pkg)
+ .write();
}
}
}
@@ -10524,6 +10743,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
synchronized (getLockObject()) {
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
setUserRestriction(who, UserManager.DISALLOW_UNMUTE_DEVICE, on);
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_MASTER_VOLUME_MUTED)
+ .setAdmin(who)
+ .setBoolean(on)
+ .write();
}
}
@@ -10553,6 +10777,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
mInjector.binderRestoreCallingIdentity(id);
}
}
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_USER_ICON)
+ .setAdmin(who)
+ .write();
}
@Override
@@ -10578,6 +10806,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
mLockPatternUtils.setLockScreenDisabled(disabled, userId);
mInjector.getIWindowManager().dismissKeyguard(null /* callback */, null /* message */);
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_KEYGUARD_DISABLED)
+ .setAdmin(who)
+ .setBoolean(disabled)
+ .write();
} catch (RemoteException e) {
// Same process, does not happen.
} finally {
@@ -10616,6 +10849,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
saveSettingsLocked(userId);
}
}
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_STATUS_BAR_DISABLED)
+ .setAdmin(who)
+ .setBoolean(disabled)
+ .write();
return true;
}
@@ -11036,6 +11274,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
mContext.sendBroadcastAsUser(
new Intent(DevicePolicyManager.ACTION_SYSTEM_UPDATE_POLICY_CHANGED),
UserHandle.SYSTEM);
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_SYSTEM_UPDATE_POLICY)
+ .setAdmin(who)
+ .setInt(policy != null ? policy.getPolicyType() : 0)
+ .write();
}
@Override
@@ -11584,11 +11827,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final long ident = mInjector.binderClearCallingIdentity();
try {
- final WifiInfo wifiInfo = mInjector.getWifiManager().getConnectionInfo();
- if (wifiInfo == null) {
+ String[] macAddresses = mInjector.getWifiManager().getFactoryMacAddresses();
+ if (macAddresses == null) {
return null;
}
- return wifiInfo.hasRealMacAddress() ? wifiInfo.getMacAddress() : null;
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.GET_WIFI_MAC_ADDRESS)
+ .setAdmin(admin)
+ .write();
+ return macAddresses.length > 0 ? macAddresses[0] : null;
} finally {
mInjector.binderRestoreCallingIdentity(ident);
}
@@ -11634,6 +11881,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) {
throw new IllegalStateException("Cannot be called with ongoing call on the device");
}
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.REBOOT)
+ .setAdmin(admin)
+ .write();
mInjector.powerManagerReboot(PowerManager.REBOOT_REQUESTED_BY_DEVICE_OWNER);
} finally {
mInjector.binderRestoreCallingIdentity(ident);
@@ -11655,6 +11906,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
saveSettingsLocked(userHandle);
}
}
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_SHORT_SUPPORT_MESSAGE)
+ .setAdmin(who)
+ .write();
}
@Override
@@ -11685,6 +11940,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
saveSettingsLocked(userHandle);
}
}
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_LONG_SUPPORT_MESSAGE)
+ .setAdmin(who)
+ .write();
}
@Override
@@ -11750,6 +12009,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
admin.organizationColor = color;
saveSettingsLocked(userHandle);
}
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_ORGANIZATION_COLOR)
+ .setAdmin(who)
+ .write();
}
@Override
@@ -13153,6 +13416,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
final long id = mInjector.binderClearCallingIdentity();
+ String ownerType = null;
try {
synchronized (getLockObject()) {
/*
@@ -13173,6 +13437,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
bundle = new PersistableBundle();
}
if (isProfileOwner(admin, callingUserId)) {
+ ownerType = ADMIN_TYPE_PROFILE_OWNER;
prepareTransfer(admin, target, bundle, callingUserId,
ADMIN_TYPE_PROFILE_OWNER);
transferProfileOwnershipLocked(admin, target, callingUserId);
@@ -13183,6 +13448,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
notifyAffiliatedProfileTransferOwnershipComplete(callingUserId);
}
} else if (isDeviceOwner(admin, callingUserId)) {
+ ownerType = ADMIN_TYPE_DEVICE_OWNER;
prepareTransfer(admin, target, bundle, callingUserId,
ADMIN_TYPE_DEVICE_OWNER);
transferDeviceOwnershipLocked(admin, target, callingUserId);
@@ -13194,6 +13460,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
} finally {
mInjector.binderRestoreCallingIdentity(id);
}
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.TRANSFER_OWNERSHIP)
+ .setAdmin(admin)
+ .setStrings(target.getPackageName(), ownerType)
+ .write();
}
private void prepareTransfer(ComponentName admin, ComponentName target,
@@ -13669,6 +13940,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
mContext, updateFileDescriptor, callback, mInjector, mConstants);
}
updateInstaller.startInstallUpdate();
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.INSTALL_SYSTEM_UPDATE)
+ .setAdmin(admin)
+ .setBoolean(isDeviceAB())
+ .write();
} finally {
mInjector.binderRestoreCallingIdentity(id);
}
@@ -13694,6 +13970,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
saveSettingsLocked(mInjector.userHandleGetCallingUserId());
}
}
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.ADD_CROSS_PROFILE_CALENDAR_PACKAGE)
+ .setAdmin(who)
+ .setStrings(packageName)
+ .write();
}
@Override
@@ -13713,6 +13994,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
saveSettingsLocked(mInjector.userHandleGetCallingUserId());
}
}
+ if (isRemoved) {
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.REMOVE_CROSS_PROFILE_CALENDAR_PACKAGE)
+ .setAdmin(who)
+ .setStrings(packageName)
+ .write();
+ }
return isRemoved;
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index e1b83fc99897..cf03d613634f 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -724,6 +724,10 @@ public final class SystemServer {
mSystemServiceManager.startService(new OverlayManagerService(mSystemContext, installer));
traceEnd();
+ traceBeginAndSlog("StartSensorPrivacyService");
+ mSystemServiceManager.startService(new SensorPrivacyService(mSystemContext));
+ traceEnd();
+
// The sensor service needs access to package manager service, app ops
// service, and permissions service, therefore we start it after them.
// Start sensor service in a separate thread. Completion should be checked
diff --git a/services/robotests/Android.mk b/services/robotests/Android.mk
index 6f10ed5f4f5c..0c9c85acf6e2 100644
--- a/services/robotests/Android.mk
+++ b/services/robotests/Android.mk
@@ -27,6 +27,7 @@ LOCAL_PRIVILEGED_MODULE := true
LOCAL_STATIC_JAVA_LIBRARIES := \
bmgrlib \
+ bu \
services.backup \
services.core \
services.net
diff --git a/services/robotests/src/com/android/commands/bu/AdbBackupTest.java b/services/robotests/src/com/android/commands/bu/AdbBackupTest.java
new file mode 100644
index 000000000000..6869f5612c1d
--- /dev/null
+++ b/services/robotests/src/com/android/commands/bu/AdbBackupTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.commands.bu;
+
+import static org.mockito.Mockito.verify;
+
+import android.app.backup.IBackupManager;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowParcelFileDescriptor;
+
+/** Unit tests for {@link com.android.commands.bu.Backup}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowParcelFileDescriptor.class})
+@Presubmit
+public class AdbBackupTest {
+ @Mock private IBackupManager mBackupManager;
+ private Backup mBackup;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mBackup = new Backup(mBackupManager);
+ }
+
+ @Test
+ public void testRun_whenUserNotSpecified_callsAdbBackupAsSystemUser() throws Exception {
+ mBackup.run(new String[] {"backup", "-all"});
+
+ verify(mBackupManager).isBackupServiceActive(UserHandle.USER_SYSTEM);
+ }
+
+ @Test
+ public void testRun_whenUserSpecified_callsBackupManagerAsSpecifiedUser() throws Exception {
+ mBackup.run(new String[] {"backup", "-user", "10", "-all"});
+
+ verify(mBackupManager).isBackupServiceActive(10);
+ }
+}
diff --git a/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java
index 96ef0ce45001..58bce1cdfbf1 100644
--- a/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java
+++ b/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java
@@ -65,6 +65,8 @@ public class BackupManagerServiceTest {
private static final String TEST_PACKAGE = "package";
private static final String TEST_TRANSPORT = "transport";
+ private static final String[] ADB_TEST_PACKAGES = {TEST_PACKAGE};
+
private static final int NON_USER_SYSTEM = UserHandle.USER_SYSTEM + 1;
private ShadowContextWrapper mShadowContext;
@@ -555,16 +557,81 @@ public class BackupManagerServiceTest {
verify(mUserBackupManagerService).hasBackupPassword();
}
+ /**
+ * Test verifying that {@link BackupManagerService#adbBackup(ParcelFileDescriptor, int, boolean,
+ * boolean, boolean, boolean, boolean, boolean, boolean, boolean, String[])} throws a
+ * {@link SecurityException} if the caller does not have INTERACT_ACROSS_USERS_FULL permission.
+ */
+ @Test
+ public void testAdbBackup_withoutPermission_throwsSecurityException() {
+ mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+
+ expectThrows(SecurityException.class,
+ () ->
+ mBackupManagerService.adbBackup(
+ /* userId */ mUserId,
+ /* parcelFileDescriptor*/ null,
+ /* includeApks */ true,
+ /* includeObbs */ true,
+ /* includeShared */ true,
+ /* doWidgets */ true,
+ /* doAllApps */ true,
+ /* includeSystem */ true,
+ /* doCompress */ true,
+ /* doKeyValue */ true,
+ null));
+
+ }
+
+ /**
+ * Test verifying that {@link BackupManagerService#adbBackup(ParcelFileDescriptor, int, boolean,
+ * boolean, boolean, boolean, boolean, boolean, boolean, boolean, String[])} does not require
+ * the caller to have INTERACT_ACROSS_USERS_FULL permission when the calling user id is the
+ * same as the target user id.
+ */
+ @Test
+ public void testAdbBackup_whenCallingUserIsTargetUser_doesntNeedPermission() throws Exception {
+ ShadowBinder.setCallingUserHandle(UserHandle.of(mUserId));
+ mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+
+ ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest();
+
+ mBackupManagerService.adbBackup(
+ /* userId */ mUserId,
+ parcelFileDescriptor,
+ /* includeApks */ true,
+ /* includeObbs */ true,
+ /* includeShared */ true,
+ /* doWidgets */ true,
+ /* doAllApps */ true,
+ /* includeSystem */ true,
+ /* doCompress */ true,
+ /* doKeyValue */ true,
+ ADB_TEST_PACKAGES);
+
+ verify(mUserBackupManagerService)
+ .adbBackup(
+ parcelFileDescriptor,
+ /* includeApks */ true,
+ /* includeObbs */ true,
+ /* includeShared */ true,
+ /* doWidgets */ true,
+ /* doAllApps */ true,
+ /* includeSystem */ true,
+ /* doCompress */ true,
+ /* doKeyValue */ true,
+ ADB_TEST_PACKAGES);
+ }
+
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testAdbBackup_callsAdbBackupForUser() throws Exception {
- File testFile = new File(mContext.getFilesDir(), "test");
- testFile.createNewFile();
- ParcelFileDescriptor parcelFileDescriptor =
- ParcelFileDescriptor.open(testFile, ParcelFileDescriptor.MODE_READ_WRITE);
- String[] packages = {TEST_PACKAGE};
+ mShadowContext.grantPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+
+ ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest();
mBackupManagerService.adbBackup(
+ /* userId */ mUserId,
parcelFileDescriptor,
/* includeApks */ true,
/* includeObbs */ true,
@@ -574,7 +641,7 @@ public class BackupManagerServiceTest {
/* includeSystem */ true,
/* doCompress */ true,
/* doKeyValue */ true,
- packages);
+ ADB_TEST_PACKAGES);
verify(mUserBackupManagerService)
.adbBackup(
@@ -587,18 +654,48 @@ public class BackupManagerServiceTest {
/* includeSystem */ true,
/* doCompress */ true,
/* doKeyValue */ true,
- packages);
+ ADB_TEST_PACKAGES);
+ }
+
+ /**
+ * Test verifying that {@link BackupManagerService#adbRestore(ParcelFileDescriptor, int)} throws
+ * a {@link SecurityException} if the caller does not have INTERACT_ACROSS_USERS_FULL
+ * permission.
+ */
+ @Test
+ public void testAdbRestore_withoutPermission_throwsSecurityException() {
+ mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+
+ expectThrows(SecurityException.class,
+ () -> mBackupManagerService.adbRestore(mUserId, null));
+
+ }
+
+ /**
+ * Test verifying that {@link BackupManagerService#adbRestore(ParcelFileDescriptor, int)} does
+ * not require the caller to have INTERACT_ACROSS_USERS_FULL permission when the calling user id
+ * is the same as the target user id.
+ */
+ @Test
+ public void testAdbRestore_whenCallingUserIsTargetUser_doesntNeedPermission() throws Exception {
+ ShadowBinder.setCallingUserHandle(UserHandle.of(mUserId));
+ mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+
+ ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest();
+
+ mBackupManagerService.adbRestore(mUserId, parcelFileDescriptor);
+
+ verify(mUserBackupManagerService).adbRestore(parcelFileDescriptor);
}
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testAdbRestore_callsAdbRestoreForUser() throws Exception {
- File testFile = new File(mContext.getFilesDir(), "test");
- testFile.createNewFile();
- ParcelFileDescriptor parcelFileDescriptor =
- ParcelFileDescriptor.open(testFile, ParcelFileDescriptor.MODE_READ_WRITE);
+ mShadowContext.grantPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+
+ ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest();
- mBackupManagerService.adbRestore(parcelFileDescriptor);
+ mBackupManagerService.adbRestore(mUserId, parcelFileDescriptor);
verify(mUserBackupManagerService).adbRestore(parcelFileDescriptor);
}
@@ -638,4 +735,10 @@ public class BackupManagerServiceTest {
verify(mUserBackupManagerService).dump(fileDescriptor, printWriter, args);
}
+
+ private ParcelFileDescriptor getFileDescriptorForAdbTest() throws Exception {
+ File testFile = new File(mContext.getFilesDir(), "test");
+ testFile.createNewFile();
+ return ParcelFileDescriptor.open(testFile, ParcelFileDescriptor.MODE_READ_WRITE);
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index 04a84081bad8..cff0521bba50 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -203,6 +203,8 @@ public class DeviceIdleControllerTest {
.strictness(Strictness.LENIENT)
.mockStatic(LocalServices.class)
.startMocking();
+ spyOn(getContext());
+ doReturn(null).when(getContext()).registerReceiver(any(), any());
doReturn(mock(ActivityManagerInternal.class))
.when(() -> LocalServices.getService(ActivityManagerInternal.class));
doReturn(mock(ActivityTaskManagerInternal.class))
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 effb5a72bd66..f1cd0cd6d30c 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
@@ -30,6 +30,7 @@ import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -62,6 +63,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobSchedulerService.Constants;
+import com.android.server.job.controllers.QuotaController.ExecutionStats;
import com.android.server.job.controllers.QuotaController.TimingSession;
import org.junit.After;
@@ -131,13 +133,18 @@ public class QuotaControllerTest {
doReturn(mock(PackageManagerInternal.class))
.when(() -> LocalServices.getService(PackageManagerInternal.class));
- // Freeze the clocks at this moment in time
+ // Freeze the clocks at 24 hours after this moment in time. Several tests create sessions
+ // in the past, and QuotaController sometimes floors values at 0, so if the test time
+ // causes sessions with negative timestamps, they will fail.
JobSchedulerService.sSystemClock =
- Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
- JobSchedulerService.sUptimeMillisClock =
- Clock.fixed(SystemClock.uptimeMillisClock().instant(), ZoneOffset.UTC);
- JobSchedulerService.sElapsedRealtimeClock =
- Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC);
+ getAdvancedClock(Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC),
+ 24 * HOUR_IN_MILLIS);
+ JobSchedulerService.sUptimeMillisClock = getAdvancedClock(
+ Clock.fixed(SystemClock.uptimeMillisClock().instant(), ZoneOffset.UTC),
+ 24 * HOUR_IN_MILLIS);
+ JobSchedulerService.sElapsedRealtimeClock = getAdvancedClock(
+ Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC),
+ 24 * HOUR_IN_MILLIS);
// Initialize real objects.
// Capture the listeners.
@@ -291,9 +298,17 @@ public class QuotaControllerTest {
mQuotaController.saveTimingSession(0, "com.android.test.stay", two);
mQuotaController.saveTimingSession(0, "com.android.test.stay", one);
+ ExecutionStats expectedStats = new ExecutionStats();
+ expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS;
+ expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
+
mQuotaController.onAppRemovedLocked("com.android.test.remove", 10001);
assertNull(mQuotaController.getTimingSessions(0, "com.android.test.remove"));
assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test.stay"));
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test.remove", RARE_INDEX));
+ assertNotEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test.stay", RARE_INDEX));
}
@Test
@@ -318,13 +333,21 @@ public class QuotaControllerTest {
mQuotaController.saveTimingSession(10, "com.android.test", two);
mQuotaController.saveTimingSession(10, "com.android.test", one);
+ ExecutionStats expectedStats = new ExecutionStats();
+ expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS;
+ expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
+
mQuotaController.onUserRemovedLocked(0);
assertNull(mQuotaController.getTimingSessions(0, "com.android.test"));
assertEquals(expected, mQuotaController.getTimingSessions(10, "com.android.test"));
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX));
+ assertNotEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(10, "com.android.test", RARE_INDEX));
}
@Test
- public void testGetTrailingExecutionTimeLocked_NoTimer() {
+ public void testUpdateExecutionStatsLocked_NoTimer() {
final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
// Added in chronological order.
mQuotaController.saveTimingSession(0, "com.android.test",
@@ -340,32 +363,288 @@ public class QuotaControllerTest {
mQuotaController.saveTimingSession(0, "com.android.test",
createTimingSession(now - 5 * MINUTE_IN_MILLIS, 4 * MINUTE_IN_MILLIS, 3));
- assertEquals(0, mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test",
- MINUTE_IN_MILLIS));
- assertEquals(2 * MINUTE_IN_MILLIS,
- mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test",
- 3 * MINUTE_IN_MILLIS));
- assertEquals(4 * MINUTE_IN_MILLIS,
- mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test",
- 5 * MINUTE_IN_MILLIS));
- assertEquals(4 * MINUTE_IN_MILLIS,
- mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test",
- 49 * MINUTE_IN_MILLIS));
- assertEquals(5 * MINUTE_IN_MILLIS,
- mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test",
- 50 * MINUTE_IN_MILLIS));
- assertEquals(6 * MINUTE_IN_MILLIS,
- mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test",
- HOUR_IN_MILLIS));
- assertEquals(11 * MINUTE_IN_MILLIS,
- mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test",
- 2 * HOUR_IN_MILLIS));
- assertEquals(12 * MINUTE_IN_MILLIS,
- mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test",
- 3 * HOUR_IN_MILLIS));
- assertEquals(22 * MINUTE_IN_MILLIS,
- mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test",
- 6 * HOUR_IN_MILLIS));
+ // Test an app that hasn't had any activity.
+ ExecutionStats expectedStats = new ExecutionStats();
+ ExecutionStats inputStats = new ExecutionStats();
+
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = 12 * HOUR_IN_MILLIS;
+ // Invalid time is now +24 hours since there are no sessions at all for the app.
+ expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS;
+ mQuotaController.updateExecutionStatsLocked(0, "com.android.test.not.run", inputStats);
+ assertEquals(expectedStats, inputStats);
+
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = MINUTE_IN_MILLIS;
+ // Invalid time is now +18 hours since there are no sessions in the window but the earliest
+ // session is 6 hours ago.
+ expectedStats.invalidTimeElapsed = now + 18 * HOUR_IN_MILLIS;
+ expectedStats.executionTimeInWindowMs = 0;
+ expectedStats.bgJobCountInWindow = 0;
+ expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInMaxPeriod = 15;
+ mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
+ assertEquals(expectedStats, inputStats);
+
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * MINUTE_IN_MILLIS;
+ // Invalid time is now since the session straddles the window cutoff time.
+ expectedStats.invalidTimeElapsed = now;
+ expectedStats.executionTimeInWindowMs = 2 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInWindow = 3;
+ expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInMaxPeriod = 15;
+ mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
+ assertEquals(expectedStats, inputStats);
+
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = 5 * MINUTE_IN_MILLIS;
+ // Invalid time is now since the start of the session is at the very edge of the window
+ // cutoff time.
+ expectedStats.invalidTimeElapsed = now;
+ expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInWindow = 3;
+ expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInMaxPeriod = 15;
+ mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
+ assertEquals(expectedStats, inputStats);
+
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = 49 * MINUTE_IN_MILLIS;
+ // Invalid time is now +44 minutes since the earliest session in the window is now-5
+ // minutes.
+ expectedStats.invalidTimeElapsed = now + 44 * MINUTE_IN_MILLIS;
+ expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInWindow = 3;
+ expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInMaxPeriod = 15;
+ mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
+ assertEquals(expectedStats, inputStats);
+
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = 50 * MINUTE_IN_MILLIS;
+ // Invalid time is now since the session is at the very edge of the window cutoff time.
+ expectedStats.invalidTimeElapsed = now;
+ expectedStats.executionTimeInWindowMs = 5 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInWindow = 4;
+ expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInMaxPeriod = 15;
+ mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
+ assertEquals(expectedStats, inputStats);
+
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = HOUR_IN_MILLIS;
+ // Invalid time is now since the start of the session is at the very edge of the window
+ // cutoff time.
+ expectedStats.invalidTimeElapsed = now;
+ expectedStats.executionTimeInWindowMs = 6 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInWindow = 5;
+ expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInMaxPeriod = 15;
+ mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
+ assertEquals(expectedStats, inputStats);
+
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
+ // Invalid time is now since the session straddles the window cutoff time.
+ expectedStats.invalidTimeElapsed = now;
+ expectedStats.executionTimeInWindowMs = 11 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInWindow = 10;
+ expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInMaxPeriod = 15;
+ expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
+ + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
+ mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
+ assertEquals(expectedStats, inputStats);
+
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * HOUR_IN_MILLIS;
+ // Invalid time is now +59 minutes since the earliest session in the window is now-121
+ // minutes.
+ expectedStats.invalidTimeElapsed = now + 59 * MINUTE_IN_MILLIS;
+ expectedStats.executionTimeInWindowMs = 12 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInWindow = 10;
+ expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInMaxPeriod = 15;
+ expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
+ + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
+ mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
+ assertEquals(expectedStats, inputStats);
+
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = 6 * HOUR_IN_MILLIS;
+ // Invalid time is now since the start of the session is at the very edge of the window
+ // cutoff time.
+ expectedStats.invalidTimeElapsed = now;
+ expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInWindow = 15;
+ expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInMaxPeriod = 15;
+ expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
+ + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
+ mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
+ assertEquals(expectedStats, inputStats);
+
+ // Make sure invalidTimeElapsed is set correctly when it's dependent on the max period.
+ mQuotaController.getTimingSessions(0, "com.android.test")
+ .add(0,
+ createTimingSession(now - (23 * HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 3));
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
+ // Invalid time is now +1 hour since the earliest session in the max period is 1 hour
+ // before the end of the max period cutoff time.
+ expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS;
+ expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInWindow = 15;
+ expectedStats.executionTimeInMaxPeriodMs = 23 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInMaxPeriod = 18;
+ expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
+ + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
+ mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
+ assertEquals(expectedStats, inputStats);
+
+ mQuotaController.getTimingSessions(0, "com.android.test")
+ .add(0,
+ createTimingSession(now - (24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS),
+ 2 * MINUTE_IN_MILLIS, 2));
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
+ // Invalid time is now since the earlist session straddles the max period cutoff time.
+ expectedStats.invalidTimeElapsed = now;
+ expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInWindow = 15;
+ expectedStats.executionTimeInMaxPeriodMs = 24 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInMaxPeriod = 20;
+ expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
+ + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
+ mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
+ assertEquals(expectedStats, inputStats);
+ }
+
+ /**
+ * Tests that getExecutionStatsLocked returns the correct stats.
+ */
+ @Test
+ public void testGetExecutionStatsLocked_Values() {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (23 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
+
+ ExecutionStats expectedStats = new ExecutionStats();
+
+ // Active
+ expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS;
+ expectedStats.invalidTimeElapsed = now + 4 * MINUTE_IN_MILLIS;
+ expectedStats.executionTimeInWindowMs = 3 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInWindow = 5;
+ expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInMaxPeriod = 20;
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test", ACTIVE_INDEX));
+
+ // Working
+ expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
+ expectedStats.invalidTimeElapsed = now;
+ expectedStats.executionTimeInWindowMs = 13 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInWindow = 10;
+ expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInMaxPeriod = 20;
+ expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS)
+ + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test", WORKING_INDEX));
+
+ // Frequent
+ expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
+ expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS;
+ expectedStats.executionTimeInWindowMs = 23 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInWindow = 15;
+ expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInMaxPeriod = 20;
+ expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS)
+ + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test", FREQUENT_INDEX));
+
+ // Rare
+ expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
+ expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS;
+ expectedStats.executionTimeInWindowMs = 33 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInWindow = 20;
+ expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
+ expectedStats.bgJobCountInMaxPeriod = 20;
+ expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS)
+ + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
+ assertEquals(expectedStats,
+ mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX));
+ }
+
+ /**
+ * Tests that getExecutionStatsLocked properly caches the stats and returns the cached object.
+ */
+ @Test
+ public void testGetExecutionStatsLocked_Caching() {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (23 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
+ mQuotaController.saveTimingSession(0, "com.android.test",
+ createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
+ final ExecutionStats originalStatsActive = mQuotaController.getExecutionStatsLocked(0,
+ "com.android.test", ACTIVE_INDEX);
+ final ExecutionStats originalStatsWorking = mQuotaController.getExecutionStatsLocked(0,
+ "com.android.test", WORKING_INDEX);
+ final ExecutionStats originalStatsFrequent = mQuotaController.getExecutionStatsLocked(0,
+ "com.android.test", FREQUENT_INDEX);
+ final ExecutionStats originalStatsRare = mQuotaController.getExecutionStatsLocked(0,
+ "com.android.test", RARE_INDEX);
+
+ // Advance clock so that the working stats shouldn't be the same.
+ advanceElapsedClock(MINUTE_IN_MILLIS);
+ // Change frequent bucket size so that the stats need to be recalculated.
+ mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 6 * HOUR_IN_MILLIS;
+ mQuotaController.onConstantsUpdatedLocked();
+
+ ExecutionStats expectedStats = new ExecutionStats();
+ expectedStats.windowSizeMs = originalStatsActive.windowSizeMs;
+ expectedStats.invalidTimeElapsed = originalStatsActive.invalidTimeElapsed;
+ expectedStats.executionTimeInWindowMs = originalStatsActive.executionTimeInWindowMs;
+ expectedStats.bgJobCountInWindow = originalStatsActive.bgJobCountInWindow;
+ expectedStats.executionTimeInMaxPeriodMs = originalStatsActive.executionTimeInMaxPeriodMs;
+ expectedStats.bgJobCountInMaxPeriod = originalStatsActive.bgJobCountInMaxPeriod;
+ expectedStats.quotaCutoffTimeElapsed = originalStatsActive.quotaCutoffTimeElapsed;
+ final ExecutionStats newStatsActive = mQuotaController.getExecutionStatsLocked(0,
+ "com.android.test", ACTIVE_INDEX);
+ // Stats for the same bucket should use the same object.
+ assertTrue(originalStatsActive == newStatsActive);
+ assertEquals(expectedStats, newStatsActive);
+
+ expectedStats.windowSizeMs = originalStatsWorking.windowSizeMs;
+ expectedStats.invalidTimeElapsed = originalStatsWorking.invalidTimeElapsed;
+ expectedStats.executionTimeInWindowMs = originalStatsWorking.executionTimeInWindowMs;
+ expectedStats.bgJobCountInWindow = originalStatsWorking.bgJobCountInWindow;
+ expectedStats.quotaCutoffTimeElapsed = originalStatsWorking.quotaCutoffTimeElapsed;
+ final ExecutionStats newStatsWorking = mQuotaController.getExecutionStatsLocked(0,
+ "com.android.test", WORKING_INDEX);
+ assertTrue(originalStatsWorking == newStatsWorking);
+ assertNotEquals(expectedStats, newStatsWorking);
+
+ expectedStats.windowSizeMs = originalStatsFrequent.windowSizeMs;
+ expectedStats.invalidTimeElapsed = originalStatsFrequent.invalidTimeElapsed;
+ expectedStats.executionTimeInWindowMs = originalStatsFrequent.executionTimeInWindowMs;
+ expectedStats.bgJobCountInWindow = originalStatsFrequent.bgJobCountInWindow;
+ expectedStats.quotaCutoffTimeElapsed = originalStatsFrequent.quotaCutoffTimeElapsed;
+ final ExecutionStats newStatsFrequent = mQuotaController.getExecutionStatsLocked(0,
+ "com.android.test", FREQUENT_INDEX);
+ assertTrue(originalStatsFrequent == newStatsFrequent);
+ assertNotEquals(expectedStats, newStatsFrequent);
+
+ expectedStats.windowSizeMs = originalStatsRare.windowSizeMs;
+ expectedStats.invalidTimeElapsed = originalStatsRare.invalidTimeElapsed;
+ expectedStats.executionTimeInWindowMs = originalStatsRare.executionTimeInWindowMs;
+ expectedStats.bgJobCountInWindow = originalStatsRare.bgJobCountInWindow;
+ expectedStats.quotaCutoffTimeElapsed = originalStatsRare.quotaCutoffTimeElapsed;
+ final ExecutionStats newStatsRare = mQuotaController.getExecutionStatsLocked(0,
+ "com.android.test", RARE_INDEX);
+ assertTrue(originalStatsRare == newStatsRare);
+ assertEquals(expectedStats, newStatsRare);
}
@Test
@@ -394,6 +673,56 @@ public class QuotaControllerTest {
}
@Test
+ public void testMaybeScheduleStartAlarmLocked_Active() {
+ // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
+ // because it schedules an alarm too. Prevent it from doing so.
+ spyOn(mQuotaController);
+ doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
+
+ // Active window size is 10 minutes.
+ final int standbyBucket = ACTIVE_INDEX;
+
+ // No sessions saved yet.
+ mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
+ standbyBucket);
+ verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ // Test with timing sessions out of window but still under max execution limit.
+ final long expectedAlarmTime =
+ (now - 18 * HOUR_IN_MILLIS) + 24 * HOUR_IN_MILLIS
+ + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - 18 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1));
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - 12 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1));
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - 7 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1));
+ mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
+ standbyBucket);
+ verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - 2 * HOUR_IN_MILLIS, 55 * MINUTE_IN_MILLIS, 1));
+ mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
+ standbyBucket);
+ verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+ JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_Active", 1);
+ mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
+ mQuotaController.prepareForExecutionLocked(jobStatus);
+ advanceElapsedClock(5 * MINUTE_IN_MILLIS);
+ // Timer has only been going for 5 minutes in the past 10 minutes, which is under the window
+ // size limit, but the total execution time for the past 24 hours is 6 hours, so the job no
+ // longer has quota.
+ assertEquals(0, mQuotaController.getRemainingExecutionTimeLocked(jobStatus));
+ mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
+ standbyBucket);
+ verify(mAlarmManager, times(1)).set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK),
+ any(), any());
+ }
+
+ @Test
public void testMaybeScheduleStartAlarmLocked_WorkingSet() {
// saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
// because it schedules an alarm too. Prevent it from doing so.
@@ -579,8 +908,8 @@ public class QuotaControllerTest {
// Start in ACTIVE bucket.
mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX);
- inOrder.verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
- any());
+ inOrder.verify(mAlarmManager, never())
+ .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
inOrder.verify(mAlarmManager, never()).cancel(any(AlarmManager.OnAlarmListener.class));
// And down from there.
@@ -620,6 +949,124 @@ public class QuotaControllerTest {
inOrder.verify(mAlarmManager, times(1)).cancel(any(AlarmManager.OnAlarmListener.class));
}
+
+ /**
+ * Tests that the start alarm is properly rescheduled if the earliest session that contributes
+ * to the app being out of quota contributes less than the quota buffer time.
+ */
+ @Test
+ public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_DefaultValues() {
+ // Use the default values
+ runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
+ mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
+ runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
+ }
+
+ @Test
+ public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedBufferSize() {
+ // Make sure any new value is used correctly.
+ mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS *= 2;
+ mQuotaController.onConstantsUpdatedLocked();
+ runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
+ mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
+ runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
+ }
+
+ @Test
+ public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedAllowedTime() {
+ // Make sure any new value is used correctly.
+ mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS /= 2;
+ mQuotaController.onConstantsUpdatedLocked();
+ runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
+ mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
+ runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
+ }
+
+ @Test
+ public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedMaxTime() {
+ // Make sure any new value is used correctly.
+ mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS /= 2;
+ mQuotaController.onConstantsUpdatedLocked();
+ runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
+ mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
+ runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
+ }
+
+ @Test
+ public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedEverything() {
+ // Make sure any new value is used correctly.
+ mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS *= 2;
+ mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS /= 2;
+ mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS /= 2;
+ mQuotaController.onConstantsUpdatedLocked();
+ runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
+ mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
+ runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
+ }
+
+ private void runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck() {
+ // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
+ // because it schedules an alarm too. Prevent it from doing so.
+ spyOn(mQuotaController);
+ doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
+
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ // Working set window size is 2 hours.
+ final int standbyBucket = WORKING_INDEX;
+ final long contributionMs = mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS / 2;
+ final long remainingTimeMs =
+ mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS - contributionMs;
+
+ // Session straddles edge of bucket window. Only the contribution should be counted towards
+ // the quota.
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (2 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS),
+ 3 * MINUTE_IN_MILLIS + contributionMs, 3));
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - HOUR_IN_MILLIS, remainingTimeMs, 2));
+ // Expected alarm time should be when the app will have QUOTA_BUFFER_MS time of quota, which
+ // is 2 hours + (QUOTA_BUFFER_MS - contributionMs) after the start of the second session.
+ final long expectedAlarmTime = now - HOUR_IN_MILLIS + 2 * HOUR_IN_MILLIS
+ + (mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS - contributionMs);
+ mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
+ standbyBucket);
+ verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+ }
+
+
+ private void runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck() {
+ // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
+ // because it schedules an alarm too. Prevent it from doing so.
+ spyOn(mQuotaController);
+ doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
+
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+ // Working set window size is 2 hours.
+ final int standbyBucket = WORKING_INDEX;
+ final long contributionMs = mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS / 2;
+ final long remainingTimeMs =
+ mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS - contributionMs;
+
+ // Session straddles edge of 24 hour window. Only the contribution should be counted towards
+ // the quota.
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - (24 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS),
+ 3 * MINUTE_IN_MILLIS + contributionMs, 3));
+ mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
+ createTimingSession(now - 20 * HOUR_IN_MILLIS, remainingTimeMs, 300));
+ // Expected alarm time should be when the app will have QUOTA_BUFFER_MS time of quota, which
+ // is 24 hours + (QUOTA_BUFFER_MS - contributionMs) after the start of the second session.
+ final long expectedAlarmTime = now - 20 * HOUR_IN_MILLIS
+ //+ mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS
+ + 24 * HOUR_IN_MILLIS
+ + (mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS - contributionMs);
+ mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
+ standbyBucket);
+ verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+ }
+
/** Tests that QuotaController doesn't throttle if throttling is turned off. */
@Test
public void testThrottleToggling() throws Exception {
@@ -652,6 +1099,7 @@ public class QuotaControllerTest {
mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = 30 * MINUTE_IN_MILLIS;
mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 45 * MINUTE_IN_MILLIS;
mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = 60 * MINUTE_IN_MILLIS;
+ mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = 3 * HOUR_IN_MILLIS;
mQuotaController.onConstantsUpdatedLocked();
@@ -662,6 +1110,7 @@ public class QuotaControllerTest {
assertEquals(45 * MINUTE_IN_MILLIS,
mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
assertEquals(60 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
+ assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
}
@Test
@@ -673,6 +1122,7 @@ public class QuotaControllerTest {
mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = -MINUTE_IN_MILLIS;
mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = -MINUTE_IN_MILLIS;
mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = -MINUTE_IN_MILLIS;
+ mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = -MINUTE_IN_MILLIS;
mQuotaController.onConstantsUpdatedLocked();
@@ -682,6 +1132,7 @@ public class QuotaControllerTest {
assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
+ assertEquals(HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
// Test larger than a day. Controller should cap at one day.
mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = 25 * HOUR_IN_MILLIS;
@@ -690,6 +1141,7 @@ public class QuotaControllerTest {
mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = 25 * HOUR_IN_MILLIS;
mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 25 * HOUR_IN_MILLIS;
mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = 25 * HOUR_IN_MILLIS;
+ mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = 25 * HOUR_IN_MILLIS;
mQuotaController.onConstantsUpdatedLocked();
@@ -699,6 +1151,7 @@ public class QuotaControllerTest {
assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
+ assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
}
/** Tests that TimingSessions aren't saved when the device is charging. */
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java
new file mode 100644
index 000000000000..494677d38c0e
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java
@@ -0,0 +1,886 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.job.controllers;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+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.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.AlarmManager;
+import android.app.job.JobInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+import android.os.SystemClock;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+import com.android.server.job.JobSchedulerService;
+import com.android.server.job.JobSchedulerService.Constants;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.time.ZoneOffset;
+
+@RunWith(AndroidJUnit4.class)
+public class TimeControllerTest {
+ private static final long SECOND_IN_MILLIS = 1000L;
+ private static final long MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS;
+ private static final long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS;
+ private static final String TAG_DEADLINE = "*job.deadline*";
+ private static final String TAG_DELAY = "*job.delay*";
+ private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
+ private static final int SOURCE_USER_ID = 0;
+
+ private Constants mConstants;
+ private TimeController mTimeController;
+
+ private MockitoSession mMockingSession;
+ @Mock
+ private AlarmManager mAlarmManager;
+ @Mock
+ private Context mContext;
+ @Mock
+ private JobSchedulerService mJobSchedulerService;
+
+ @Before
+ public void setUp() {
+ mMockingSession = mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .mockStatic(LocalServices.class)
+ .startMocking();
+ // Use default constants for now.
+ mConstants = new Constants();
+
+ // Called in StateController constructor.
+ when(mJobSchedulerService.getTestableContext()).thenReturn(mContext);
+ when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService);
+ when(mJobSchedulerService.getConstants()).thenReturn(mConstants);
+ // Called in TimeController constructor.
+ when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager);
+ // Used in JobStatus.
+ doReturn(mock(PackageManagerInternal.class))
+ .when(() -> LocalServices.getService(PackageManagerInternal.class));
+
+ // Freeze the clocks at this moment in time
+ JobSchedulerService.sSystemClock =
+ Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
+ JobSchedulerService.sUptimeMillisClock =
+ Clock.fixed(SystemClock.uptimeMillisClock().instant(), ZoneOffset.UTC);
+ JobSchedulerService.sElapsedRealtimeClock =
+ Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC);
+
+ // Initialize real objects.
+ mTimeController = new TimeController(mJobSchedulerService);
+ spyOn(mTimeController);
+ }
+
+ @After
+ public void tearDown() {
+ if (mMockingSession != null) {
+ mMockingSession.finishMocking();
+ }
+ }
+
+ private Clock getAdvancedClock(Clock clock, long incrementMs) {
+ return Clock.offset(clock, Duration.ofMillis(incrementMs));
+ }
+
+ private void advanceElapsedClock(long incrementMs) {
+ JobSchedulerService.sElapsedRealtimeClock = getAdvancedClock(
+ JobSchedulerService.sElapsedRealtimeClock, incrementMs);
+ }
+
+ private static JobInfo.Builder createJob() {
+ return new JobInfo.Builder(101, new ComponentName("foo", "bar"));
+ }
+
+ private JobStatus createJobStatus(String testTag, JobInfo.Builder job) {
+ JobInfo jobInfo = job.build();
+ return JobStatus.createFromJobInfo(
+ jobInfo, 1000, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
+ }
+
+ @Test
+ public void testMaybeStartTrackingJobLocked_AlreadySatisfied() {
+ JobStatus delaySatisfied = createJobStatus(
+ "testMaybeStartTrackingJobLocked_AlreadySatisfied",
+ createJob().setMinimumLatency(1));
+ JobStatus deadlineSatisfied = createJobStatus(
+ "testMaybeStartTrackingJobLocked_AlreadySatisfied",
+ createJob().setOverrideDeadline(1));
+
+ advanceElapsedClock(5);
+
+ mTimeController.maybeStartTrackingJobLocked(delaySatisfied, null);
+ mTimeController.maybeStartTrackingJobLocked(deadlineSatisfied, null);
+ verify(mAlarmManager, never())
+ .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+ }
+
+ @Test
+ public void testMaybeStartTrackingJobLocked_DelayInOrder_NoSkipping() {
+ mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false;
+ mTimeController.onConstantsUpdatedLocked();
+
+ runTestMaybeStartTrackingJobLocked_DelayInOrder();
+ }
+
+ @Test
+ public void testMaybeStartTrackingJobLocked_DelayInOrder_WithSkipping_AllReady() {
+ mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true;
+ mTimeController.onConstantsUpdatedLocked();
+
+ doReturn(true).when(mTimeController).wouldBeReadyWithConstraintLocked(any(), anyInt());
+
+ runTestMaybeStartTrackingJobLocked_DelayInOrder();
+ }
+
+ private void runTestMaybeStartTrackingJobLocked_DelayInOrder() {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+ JobStatus jobLatest = createJobStatus("testMaybeStartTrackingJobLocked_DelayInOrder",
+ createJob().setMinimumLatency(HOUR_IN_MILLIS));
+ JobStatus jobMiddle = createJobStatus("testMaybeStartTrackingJobLocked_DelayInOrder",
+ createJob().setMinimumLatency(30 * MINUTE_IN_MILLIS));
+ JobStatus jobEarliest = createJobStatus("testMaybeStartTrackingJobLocked_DelayInOrder",
+ createJob().setMinimumLatency(5 * MINUTE_IN_MILLIS));
+
+ InOrder inOrder = inOrder(mAlarmManager);
+
+ mTimeController.maybeStartTrackingJobLocked(jobEarliest, null);
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY),
+ any(), any(), any());
+ mTimeController.maybeStartTrackingJobLocked(jobMiddle, null);
+ inOrder.verify(mAlarmManager, never())
+ .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+ mTimeController.maybeStartTrackingJobLocked(jobLatest, null);
+ inOrder.verify(mAlarmManager, never())
+ .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+ }
+
+ @Test
+ public void testMaybeStartTrackingJobLocked_DelayInOrder_WithSkipping_SomeNotReady() {
+ mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true;
+ mTimeController.onConstantsUpdatedLocked();
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+ JobStatus jobLatest = createJobStatus("testMaybeStartTrackingJobLocked_DelayInOrder",
+ createJob().setMinimumLatency(HOUR_IN_MILLIS));
+ JobStatus jobMiddle = createJobStatus("testMaybeStartTrackingJobLocked_DelayInOrder",
+ createJob().setMinimumLatency(30 * MINUTE_IN_MILLIS));
+ JobStatus jobEarliest = createJobStatus("testMaybeStartTrackingJobLocked_DelayInOrder",
+ createJob().setMinimumLatency(5 * MINUTE_IN_MILLIS));
+
+ doReturn(true).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt());
+ doReturn(false).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt());
+ doReturn(true).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
+
+ InOrder inOrder = inOrder(mAlarmManager);
+
+ mTimeController.maybeStartTrackingJobLocked(jobEarliest, null);
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY),
+ any(), any(), any());
+ mTimeController.maybeStartTrackingJobLocked(jobMiddle, null);
+ inOrder.verify(mAlarmManager, never())
+ .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+ mTimeController.maybeStartTrackingJobLocked(jobLatest, null);
+ inOrder.verify(mAlarmManager, never())
+ .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+ }
+
+ @Test
+ public void testMaybeStartTrackingJobLocked_DelayReverseOrder_NoSkipping() {
+ mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false;
+ mTimeController.onConstantsUpdatedLocked();
+
+ runTestMaybeStartTrackingJobLocked_DelayReverseOrder();
+ }
+
+ @Test
+ public void testMaybeStartTrackingJobLocked_DelayReverseOrder_WithSkipping_AllReady() {
+ mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true;
+ mTimeController.onConstantsUpdatedLocked();
+
+ doReturn(true).when(mTimeController).wouldBeReadyWithConstraintLocked(any(), anyInt());
+
+ runTestMaybeStartTrackingJobLocked_DelayReverseOrder();
+ }
+
+ private void runTestMaybeStartTrackingJobLocked_DelayReverseOrder() {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+ JobStatus jobLatest = createJobStatus("testMaybeStartTrackingJobLocked_DelayReverseOrder",
+ createJob().setMinimumLatency(HOUR_IN_MILLIS));
+ JobStatus jobMiddle = createJobStatus("testMaybeStartTrackingJobLocked_DelayReverseOrder",
+ createJob().setMinimumLatency(30 * MINUTE_IN_MILLIS));
+ JobStatus jobEarliest = createJobStatus("testMaybeStartTrackingJobLocked_DelayReverseOrder",
+ createJob().setMinimumLatency(5 * MINUTE_IN_MILLIS));
+
+ InOrder inOrder = inOrder(mAlarmManager);
+
+ mTimeController.maybeStartTrackingJobLocked(jobLatest, null);
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY), any(),
+ any(), any());
+ mTimeController.maybeStartTrackingJobLocked(jobMiddle, null);
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + 30 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY),
+ any(), any(), any());
+ mTimeController.maybeStartTrackingJobLocked(jobEarliest, null);
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY),
+ any(), any(), any());
+ }
+
+ @Test
+ public void testMaybeStartTrackingJobLocked_DelayReverseOrder_WithSkipping_SomeNotReady() {
+ mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true;
+ mTimeController.onConstantsUpdatedLocked();
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+ JobStatus jobLatest = createJobStatus("testMaybeStartTrackingJobLocked_DelayReverseOrder",
+ createJob().setMinimumLatency(HOUR_IN_MILLIS));
+ JobStatus jobMiddle = createJobStatus("testMaybeStartTrackingJobLocked_DelayReverseOrder",
+ createJob().setMinimumLatency(30 * MINUTE_IN_MILLIS));
+ JobStatus jobEarliest = createJobStatus("testMaybeStartTrackingJobLocked_DelayReverseOrder",
+ createJob().setMinimumLatency(5 * MINUTE_IN_MILLIS));
+
+ doReturn(true).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt());
+ doReturn(false).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt());
+ doReturn(true).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
+
+ InOrder inOrder = inOrder(mAlarmManager);
+
+ mTimeController.maybeStartTrackingJobLocked(jobLatest, null);
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY), any(),
+ any(), any());
+ mTimeController.maybeStartTrackingJobLocked(jobMiddle, null);
+ // Middle alarm shouldn't be set since it won't be ready.
+ inOrder.verify(mAlarmManager, never())
+ .set(anyInt(), anyLong(), anyLong(), anyLong(), eq(TAG_DELAY), any(), any(), any());
+ mTimeController.maybeStartTrackingJobLocked(jobEarliest, null);
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY),
+ any(), any(), any());
+ }
+
+ @Test
+ public void testMaybeStartTrackingJobLocked_DeadlineInOrder_NoSkipping() {
+ mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false;
+ mTimeController.onConstantsUpdatedLocked();
+
+ runTestMaybeStartTrackingJobLocked_DeadlineInOrder();
+ }
+
+ @Test
+ public void testMaybeStartTrackingJobLocked_DeadlineInOrder_WithSkipping_AllReady() {
+ mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true;
+ mTimeController.onConstantsUpdatedLocked();
+
+ doReturn(true).when(mTimeController).wouldBeReadyWithConstraintLocked(any(), anyInt());
+
+ runTestMaybeStartTrackingJobLocked_DeadlineInOrder();
+ }
+
+ private void runTestMaybeStartTrackingJobLocked_DeadlineInOrder() {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+ JobStatus jobLatest = createJobStatus("testMaybeStartTrackingJobLocked_DeadlineInOrder",
+ createJob().setOverrideDeadline(HOUR_IN_MILLIS));
+ JobStatus jobMiddle = createJobStatus("testMaybeStartTrackingJobLocked_DeadlineInOrder",
+ createJob().setOverrideDeadline(30 * MINUTE_IN_MILLIS));
+ JobStatus jobEarliest = createJobStatus("testMaybeStartTrackingJobLocked_DeadlineInOrder",
+ createJob().setOverrideDeadline(5 * MINUTE_IN_MILLIS));
+
+ InOrder inOrder = inOrder(mAlarmManager);
+
+ mTimeController.maybeStartTrackingJobLocked(jobEarliest, null);
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(),
+ eq(TAG_DEADLINE), any(), any(), any());
+ mTimeController.maybeStartTrackingJobLocked(jobMiddle, null);
+ inOrder.verify(mAlarmManager, never())
+ .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+ mTimeController.maybeStartTrackingJobLocked(jobLatest, null);
+ inOrder.verify(mAlarmManager, never())
+ .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+ }
+
+ @Test
+ public void testMaybeStartTrackingJobLocked_DeadlineInOrder_WithSkipping_SomeNotReady() {
+ mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true;
+ mTimeController.onConstantsUpdatedLocked();
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+ JobStatus jobLatest = createJobStatus("testMaybeStartTrackingJobLocked_DeadlineInOrder",
+ createJob().setOverrideDeadline(HOUR_IN_MILLIS));
+ JobStatus jobMiddle = createJobStatus("testMaybeStartTrackingJobLocked_DeadlineInOrder",
+ createJob().setOverrideDeadline(30 * MINUTE_IN_MILLIS));
+ JobStatus jobEarliest = createJobStatus("testMaybeStartTrackingJobLocked_DeadlineInOrder",
+ createJob().setOverrideDeadline(5 * MINUTE_IN_MILLIS));
+
+ doReturn(true).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt());
+ doReturn(false).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt());
+ doReturn(true).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
+
+ InOrder inOrder = inOrder(mAlarmManager);
+
+ mTimeController.maybeStartTrackingJobLocked(jobEarliest, null);
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(),
+ eq(TAG_DEADLINE), any(), any(), any());
+ mTimeController.maybeStartTrackingJobLocked(jobMiddle, null);
+ inOrder.verify(mAlarmManager, never())
+ .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+ mTimeController.maybeStartTrackingJobLocked(jobLatest, null);
+ inOrder.verify(mAlarmManager, never())
+ .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+ }
+
+ @Test
+ public void testMaybeStartTrackingJobLocked_DeadlineReverseOrder_NoSkipping() {
+ mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false;
+ mTimeController.onConstantsUpdatedLocked();
+
+ runTestMaybeStartTrackingJobLocked_DeadlineReverseOrder();
+ }
+
+ @Test
+ public void testMaybeStartTrackingJobLocked_DeadlineReverseOrder_WithSkipping_AllReady() {
+ mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true;
+ mTimeController.onConstantsUpdatedLocked();
+
+ doReturn(true).when(mTimeController).wouldBeReadyWithConstraintLocked(any(), anyInt());
+
+ runTestMaybeStartTrackingJobLocked_DeadlineReverseOrder();
+ }
+
+ private void runTestMaybeStartTrackingJobLocked_DeadlineReverseOrder() {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+ JobStatus jobLatest = createJobStatus(
+ "testMaybeStartTrackingJobLocked_DeadlineReverseOrder",
+ createJob().setOverrideDeadline(HOUR_IN_MILLIS));
+ JobStatus jobMiddle = createJobStatus(
+ "testMaybeStartTrackingJobLocked_DeadlineReverseOrder",
+ createJob().setOverrideDeadline(30 * MINUTE_IN_MILLIS));
+ JobStatus jobEarliest = createJobStatus(
+ "testMaybeStartTrackingJobLocked_DeadlineReverseOrder",
+ createJob().setOverrideDeadline(5 * MINUTE_IN_MILLIS));
+
+ InOrder inOrder = inOrder(mAlarmManager);
+
+ mTimeController.maybeStartTrackingJobLocked(jobLatest, null);
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DEADLINE),
+ any(), any(), any());
+ mTimeController.maybeStartTrackingJobLocked(jobMiddle, null);
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + 30 * MINUTE_IN_MILLIS), anyLong(), anyLong(),
+ eq(TAG_DEADLINE), any(), any(), any());
+ mTimeController.maybeStartTrackingJobLocked(jobEarliest, null);
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(),
+ eq(TAG_DEADLINE), any(), any(), any());
+ }
+
+ @Test
+ public void testMaybeStartTrackingJobLocked_DeadlineReverseOrder_WithSkipping_SomeNotReady() {
+ mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true;
+ mTimeController.onConstantsUpdatedLocked();
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+ JobStatus jobLatest = createJobStatus(
+ "testMaybeStartTrackingJobLocked_DeadlineReverseOrder",
+ createJob().setOverrideDeadline(HOUR_IN_MILLIS));
+ JobStatus jobMiddle = createJobStatus(
+ "testMaybeStartTrackingJobLocked_DeadlineReverseOrder",
+ createJob().setOverrideDeadline(30 * MINUTE_IN_MILLIS));
+ JobStatus jobEarliest = createJobStatus(
+ "testMaybeStartTrackingJobLocked_DeadlineReverseOrder",
+ createJob().setOverrideDeadline(5 * MINUTE_IN_MILLIS));
+
+ doReturn(true).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt());
+ doReturn(false).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt());
+ doReturn(true).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
+
+ InOrder inOrder = inOrder(mAlarmManager);
+
+ mTimeController.maybeStartTrackingJobLocked(jobLatest, null);
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DEADLINE),
+ any(), any(), any());
+ mTimeController.maybeStartTrackingJobLocked(jobMiddle, null);
+ // Middle alarm should be skipped since the job wouldn't be ready.
+ inOrder.verify(mAlarmManager, never())
+ .set(anyInt(), anyLong(), anyLong(), anyLong(), eq(TAG_DEADLINE), any(), any(),
+ any());
+ mTimeController.maybeStartTrackingJobLocked(jobEarliest, null);
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(),
+ eq(TAG_DEADLINE), any(), any(), any());
+ }
+
+ @Test
+ public void testJobSkipToggling() {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+ JobStatus jobLatest = createJobStatus(
+ "testMaybeStartTrackingJobLocked_DeadlineReverseOrder",
+ createJob().setOverrideDeadline(HOUR_IN_MILLIS));
+ JobStatus jobEarliest = createJobStatus(
+ "testMaybeStartTrackingJobLocked_DeadlineReverseOrder",
+ createJob().setOverrideDeadline(5 * MINUTE_IN_MILLIS));
+
+ doReturn(true).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt());
+ doReturn(false).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
+
+ // Starting off with the skipping off, we should still set an alarm for the earlier job.
+ mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false;
+ mTimeController.onConstantsUpdatedLocked();
+ InOrder inOrder = inOrder(mAlarmManager);
+
+ mTimeController.maybeStartTrackingJobLocked(jobEarliest, null);
+ mTimeController.maybeStartTrackingJobLocked(jobLatest, null);
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(),
+ eq(TAG_DEADLINE), any(), any(), any());
+
+ // Turn it on, use alarm for later job.
+ mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true;
+ mTimeController.onConstantsUpdatedLocked();
+
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DEADLINE),
+ any(), any(), any());
+
+ // Back off, use alarm for earlier job.
+ mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false;
+ mTimeController.onConstantsUpdatedLocked();
+
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(),
+ eq(TAG_DEADLINE), any(), any(), any());
+ }
+
+ @Test
+ public void testCheckExpiredDelaysAndResetAlarm_NoSkipping() {
+ mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false;
+ mTimeController.onConstantsUpdatedLocked();
+
+ runTestCheckExpiredDelaysAndResetAlarm();
+ }
+
+ @Test
+ public void testCheckExpiredDelaysAndResetAlarm_WithSkipping_AllReady() {
+ mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true;
+ mTimeController.onConstantsUpdatedLocked();
+
+ doReturn(true).when(mTimeController).wouldBeReadyWithConstraintLocked(any(), anyInt());
+
+ runTestCheckExpiredDelaysAndResetAlarm();
+ }
+
+ private void runTestCheckExpiredDelaysAndResetAlarm() {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+ JobStatus jobLatest = createJobStatus("testCheckExpiredDelaysAndResetAlarm",
+ createJob().setMinimumLatency(HOUR_IN_MILLIS));
+ JobStatus jobMiddle = createJobStatus("testCheckExpiredDelaysAndResetAlarm",
+ createJob().setMinimumLatency(30 * MINUTE_IN_MILLIS));
+ JobStatus jobEarliest = createJobStatus("testCheckExpiredDelaysAndResetAlarm",
+ createJob().setMinimumLatency(5 * MINUTE_IN_MILLIS));
+
+ InOrder inOrder = inOrder(mAlarmManager);
+
+ mTimeController.maybeStartTrackingJobLocked(jobLatest, null);
+ mTimeController.maybeStartTrackingJobLocked(jobMiddle, null);
+ mTimeController.maybeStartTrackingJobLocked(jobEarliest, null);
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY),
+ any(), any(), any());
+
+ advanceElapsedClock(10 * MINUTE_IN_MILLIS);
+
+ mTimeController.checkExpiredDelaysAndResetAlarm();
+ assertTrue(jobEarliest.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY));
+ assertFalse(jobMiddle.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY));
+ assertFalse(jobLatest.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY));
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + 30 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY),
+ any(), any(), any());
+
+ advanceElapsedClock(30 * MINUTE_IN_MILLIS);
+
+ mTimeController.checkExpiredDelaysAndResetAlarm();
+ assertTrue(jobEarliest.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY));
+ assertTrue(jobMiddle.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY));
+ assertFalse(jobLatest.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY));
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY), any(),
+ any(), any());
+
+ advanceElapsedClock(30 * MINUTE_IN_MILLIS);
+
+ mTimeController.checkExpiredDelaysAndResetAlarm();
+ assertTrue(jobEarliest.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY));
+ assertTrue(jobMiddle.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY));
+ assertTrue(jobLatest.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY));
+ inOrder.verify(mAlarmManager, never())
+ .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+ }
+
+ @Test
+ public void testCheckExpiredDelaysAndResetAlarm_WithSkipping_SomeNotReady() {
+ mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true;
+ mTimeController.onConstantsUpdatedLocked();
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+ JobStatus jobLatest = createJobStatus("testCheckExpiredDelaysAndResetAlarm",
+ createJob().setMinimumLatency(HOUR_IN_MILLIS));
+ JobStatus jobMiddle = createJobStatus("testCheckExpiredDelaysAndResetAlarm",
+ createJob().setMinimumLatency(30 * MINUTE_IN_MILLIS));
+ JobStatus jobEarliest = createJobStatus("testCheckExpiredDelaysAndResetAlarm",
+ createJob().setMinimumLatency(5 * MINUTE_IN_MILLIS));
+
+ doReturn(true).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt());
+ doReturn(false).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt());
+ doReturn(true).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
+
+ InOrder inOrder = inOrder(mAlarmManager);
+
+ mTimeController.maybeStartTrackingJobLocked(jobLatest, null);
+ mTimeController.maybeStartTrackingJobLocked(jobMiddle, null);
+ mTimeController.maybeStartTrackingJobLocked(jobEarliest, null);
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY),
+ any(), any(), any());
+
+ advanceElapsedClock(10 * MINUTE_IN_MILLIS);
+
+ mTimeController.checkExpiredDelaysAndResetAlarm();
+ assertTrue(jobEarliest.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY));
+ assertFalse(jobMiddle.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY));
+ assertFalse(jobLatest.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY));
+ // Middle job wouldn't be ready, so its alarm should be skipped.
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY), any(),
+ any(), any());
+
+ advanceElapsedClock(55 * MINUTE_IN_MILLIS);
+
+ mTimeController.checkExpiredDelaysAndResetAlarm();
+ assertTrue(jobEarliest.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY));
+ assertTrue(jobMiddle.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY));
+ assertTrue(jobLatest.isConstraintSatisfied(JobStatus.CONSTRAINT_TIMING_DELAY));
+ inOrder.verify(mAlarmManager, never())
+ .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+ }
+
+ @Test
+ public void testCheckExpiredDeadlinesAndResetAlarm_NoSkipping() {
+ mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false;
+ mTimeController.onConstantsUpdatedLocked();
+
+ runTestCheckExpiredDeadlinesAndResetAlarm();
+ }
+
+ @Test
+ public void testCheckExpiredDeadlinesAndResetAlarm_WithSkipping_AllReady() {
+ mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true;
+ mTimeController.onConstantsUpdatedLocked();
+
+ doReturn(true).when(mTimeController).wouldBeReadyWithConstraintLocked(any(), anyInt());
+
+ runTestCheckExpiredDeadlinesAndResetAlarm();
+ }
+
+ private void runTestCheckExpiredDeadlinesAndResetAlarm() {
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+ JobStatus jobLatest = createJobStatus("testCheckExpiredDeadlinesAndResetAlarm",
+ createJob().setOverrideDeadline(HOUR_IN_MILLIS));
+ JobStatus jobMiddle = createJobStatus("testCheckExpiredDeadlinesAndResetAlarm",
+ createJob().setOverrideDeadline(30 * MINUTE_IN_MILLIS));
+ JobStatus jobEarliest = createJobStatus("testCheckExpiredDeadlinesAndResetAlarm",
+ createJob().setOverrideDeadline(5 * MINUTE_IN_MILLIS));
+
+ InOrder inOrder = inOrder(mAlarmManager);
+
+ mTimeController.maybeStartTrackingJobLocked(jobLatest, null);
+ mTimeController.maybeStartTrackingJobLocked(jobMiddle, null);
+ mTimeController.maybeStartTrackingJobLocked(jobEarliest, null);
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(),
+ eq(TAG_DEADLINE), any(), any(), any());
+
+ advanceElapsedClock(10 * MINUTE_IN_MILLIS);
+
+ mTimeController.checkExpiredDeadlinesAndResetAlarm();
+ assertTrue(jobEarliest.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
+ assertFalse(jobMiddle.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
+ assertFalse(jobLatest.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + 30 * MINUTE_IN_MILLIS), anyLong(), anyLong(),
+ eq(TAG_DEADLINE), any(), any(), any());
+
+ advanceElapsedClock(30 * MINUTE_IN_MILLIS);
+
+ mTimeController.checkExpiredDeadlinesAndResetAlarm();
+ assertTrue(jobEarliest.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
+ assertTrue(jobMiddle.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
+ assertFalse(jobLatest.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DEADLINE),
+ any(), any(), any());
+
+ advanceElapsedClock(30 * MINUTE_IN_MILLIS);
+
+ mTimeController.checkExpiredDeadlinesAndResetAlarm();
+ assertTrue(jobEarliest.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
+ assertTrue(jobMiddle.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
+ assertTrue(jobLatest.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
+ inOrder.verify(mAlarmManager, never())
+ .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+ }
+
+ @Test
+ public void testCheckExpiredDeadlinesAndResetAlarm_WithSkipping_SomeNotReady() {
+ mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true;
+ mTimeController.onConstantsUpdatedLocked();
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+ JobStatus jobLatest = createJobStatus("testCheckExpiredDeadlinesAndResetAlarm",
+ createJob().setOverrideDeadline(HOUR_IN_MILLIS));
+ JobStatus jobMiddle = createJobStatus("testCheckExpiredDeadlinesAndResetAlarm",
+ createJob().setOverrideDeadline(30 * MINUTE_IN_MILLIS));
+ JobStatus jobEarliest = createJobStatus("testCheckExpiredDeadlinesAndResetAlarm",
+ createJob().setOverrideDeadline(5 * MINUTE_IN_MILLIS));
+
+ doReturn(true).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt());
+ doReturn(false).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt());
+ doReturn(true).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
+
+ InOrder inOrder = inOrder(mAlarmManager);
+
+ mTimeController.maybeStartTrackingJobLocked(jobLatest, null);
+ mTimeController.maybeStartTrackingJobLocked(jobMiddle, null);
+ mTimeController.maybeStartTrackingJobLocked(jobEarliest, null);
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(),
+ eq(TAG_DEADLINE), any(), any(), any());
+
+ advanceElapsedClock(10 * MINUTE_IN_MILLIS);
+
+ mTimeController.checkExpiredDeadlinesAndResetAlarm();
+ assertTrue(jobEarliest.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
+ assertFalse(jobMiddle.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
+ assertFalse(jobLatest.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
+ // Middle job wouldn't be ready, so its alarm should be skipped.
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DEADLINE),
+ any(), any(), any());
+
+ advanceElapsedClock(55 * MINUTE_IN_MILLIS);
+
+ mTimeController.checkExpiredDeadlinesAndResetAlarm();
+ assertTrue(jobEarliest.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
+ assertTrue(jobMiddle.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
+ assertTrue(jobLatest.isConstraintSatisfied(JobStatus.CONSTRAINT_DEADLINE));
+ inOrder.verify(mAlarmManager, never())
+ .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+ }
+
+ @Test
+ public void testEvaluateStateLocked_SkippingOff() {
+ mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = false;
+ mTimeController.onConstantsUpdatedLocked();
+ JobStatus job = createJobStatus("testEvaluateStateLocked_SkippingOff",
+ createJob().setOverrideDeadline(HOUR_IN_MILLIS));
+
+ mTimeController.evaluateStateLocked(job);
+ verify(mAlarmManager, never())
+ .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+ }
+
+ @Test
+ public void testEvaluateStateLocked_SkippingOn_Delay() {
+ mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true;
+ mTimeController.onConstantsUpdatedLocked();
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+ JobStatus jobLatest = createJobStatus("testEvaluateStateLocked_SkippingOn_Delay",
+ createJob().setMinimumLatency(HOUR_IN_MILLIS));
+ JobStatus jobMiddle = createJobStatus("testEvaluateStateLocked_SkippingOn_Delay",
+ createJob().setMinimumLatency(30 * MINUTE_IN_MILLIS));
+ JobStatus jobEarliest = createJobStatus("testEvaluateStateLocked_SkippingOn_Delay",
+ createJob().setMinimumLatency(5 * MINUTE_IN_MILLIS));
+
+ doReturn(false).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt());
+ doReturn(true).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt());
+ doReturn(false).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
+
+ InOrder inOrder = inOrder(mAlarmManager);
+
+ mTimeController.maybeStartTrackingJobLocked(jobLatest, null);
+ mTimeController.maybeStartTrackingJobLocked(jobMiddle, null);
+ mTimeController.maybeStartTrackingJobLocked(jobEarliest, null);
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + 30 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY),
+ any(), any(), any());
+
+ // Test evaluating something after the current deadline.
+ doReturn(true).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt());
+ mTimeController.evaluateStateLocked(jobLatest);
+ inOrder.verify(mAlarmManager, never())
+ .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+
+ // Test evaluating something before the current deadline.
+ doReturn(true).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
+ mTimeController.evaluateStateLocked(jobEarliest);
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(), eq(TAG_DELAY),
+ any(), any(), any());
+ // Job goes back to not being ready. Middle is still true, so use that alarm.
+ doReturn(false).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
+ mTimeController.evaluateStateLocked(jobEarliest);
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + 30 * MINUTE_IN_MILLIS), anyLong(), anyLong(),
+ eq(TAG_DELAY), any(), any(), any());
+ // Turn middle off. Latest is true, so use that alarm.
+ doReturn(false).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt());
+ mTimeController.evaluateStateLocked(jobMiddle);
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(),
+ eq(TAG_DELAY), any(), any(), any());
+ }
+
+ @Test
+ public void testEvaluateStateLocked_SkippingOn_Deadline() {
+ mConstants.TIME_CONTROLLER_SKIP_NOT_READY_JOBS = true;
+ mTimeController.onConstantsUpdatedLocked();
+ final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
+
+ JobStatus jobLatest = createJobStatus("testEvaluateStateLocked_SkippingOn_Deadline",
+ createJob().setOverrideDeadline(HOUR_IN_MILLIS));
+ JobStatus jobMiddle = createJobStatus("testEvaluateStateLocked_SkippingOn_Deadline",
+ createJob().setOverrideDeadline(30 * MINUTE_IN_MILLIS));
+ JobStatus jobEarliest = createJobStatus("testEvaluateStateLocked_SkippingOn_Deadline",
+ createJob().setOverrideDeadline(5 * MINUTE_IN_MILLIS));
+
+ doReturn(false).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt());
+ doReturn(true).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt());
+ doReturn(false).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
+
+ InOrder inOrder = inOrder(mAlarmManager);
+
+ mTimeController.maybeStartTrackingJobLocked(jobLatest, null);
+ mTimeController.maybeStartTrackingJobLocked(jobMiddle, null);
+ mTimeController.maybeStartTrackingJobLocked(jobEarliest, null);
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + 30 * MINUTE_IN_MILLIS), anyLong(), anyLong(),
+ eq(TAG_DEADLINE), any(), any(), any());
+
+ // Test evaluating something after the current deadline.
+ doReturn(true).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobLatest), anyInt());
+ mTimeController.evaluateStateLocked(jobLatest);
+ inOrder.verify(mAlarmManager, never())
+ .set(anyInt(), anyLong(), anyLong(), anyLong(), anyString(), any(), any(), any());
+
+ // Test evaluating something before the current deadline.
+ doReturn(true).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
+ mTimeController.evaluateStateLocked(jobEarliest);
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + 5 * MINUTE_IN_MILLIS), anyLong(), anyLong(),
+ eq(TAG_DEADLINE), any(), any(), any());
+ // Job goes back to not being ready. Middle is still true, so use that alarm.
+ doReturn(false).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobEarliest), anyInt());
+ mTimeController.evaluateStateLocked(jobEarliest);
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + 30 * MINUTE_IN_MILLIS), anyLong(), anyLong(),
+ eq(TAG_DEADLINE), any(), any(), any());
+ // Turn middle off. Latest is true, so use that alarm.
+ doReturn(false).when(mTimeController)
+ .wouldBeReadyWithConstraintLocked(eq(jobMiddle), anyInt());
+ mTimeController.evaluateStateLocked(jobMiddle);
+ inOrder.verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + HOUR_IN_MILLIS), anyLong(), anyLong(),
+ eq(TAG_DEADLINE), any(), any(), any());
+ }
+}
diff --git a/services/tests/servicestests/res/values/strings.xml b/services/tests/servicestests/res/values/strings.xml
index 57da0af42a88..50ccd1f769f5 100644
--- a/services/tests/servicestests/res/values/strings.xml
+++ b/services/tests/servicestests/res/values/strings.xml
@@ -32,4 +32,6 @@
<string name="config_batterySaverDeviceSpecificConfig_1"></string>
<string name="config_batterySaverDeviceSpecificConfig_2">cpufreq-n=1:123/2:456</string>
<string name="config_batterySaverDeviceSpecificConfig_3">cpufreq-n=2:222,cpufreq-i=3:333/4:444</string>
+ <string name="module_1_name" translatable="false">module_1_name</string>
+ <string name="module_2_name" translatable="false">module_2_name</string>
</resources>
diff --git a/services/tests/servicestests/res/xml/unparseable_metadata1.xml b/services/tests/servicestests/res/xml/unparseable_metadata1.xml
new file mode 100644
index 000000000000..73967f113184
--- /dev/null
+++ b/services/tests/servicestests/res/xml/unparseable_metadata1.xml
@@ -0,0 +1,4 @@
+<not-module-metadata>
+ <module name="@string/module_1_name" packageName="com.android.module1" isHidden="false"/>
+ <module name="@string/module_2_name" packageName="com.android.module2" isHidden="true"/>
+</not-module-metadata>
diff --git a/services/tests/servicestests/res/xml/unparseable_metadata2.xml b/services/tests/servicestests/res/xml/unparseable_metadata2.xml
new file mode 100644
index 000000000000..bb5a1b267012
--- /dev/null
+++ b/services/tests/servicestests/res/xml/unparseable_metadata2.xml
@@ -0,0 +1,4 @@
+<module-metadata>
+ <module name="@string/module_1_name" packageName="com.android.module1" isHidden="false"/>
+ <not-module name="@string/module_2_name" packageName="com.android.module2" isHidden="true"/>
+</module-metadata>
diff --git a/services/tests/servicestests/res/xml/well_formed_metadata.xml b/services/tests/servicestests/res/xml/well_formed_metadata.xml
new file mode 100644
index 000000000000..17cc36945207
--- /dev/null
+++ b/services/tests/servicestests/res/xml/well_formed_metadata.xml
@@ -0,0 +1,4 @@
+<module-metadata>
+ <module name="@string/module_1_name" packageName="com.android.module1" isHidden="false"/>
+ <module name="@string/module_2_name" packageName="com.android.module2" isHidden="true"/>
+</module-metadata>
diff --git a/services/tests/servicestests/res/xml/well_formed_metadata2.xml b/services/tests/servicestests/res/xml/well_formed_metadata2.xml
new file mode 100644
index 000000000000..47279e657bc4
--- /dev/null
+++ b/services/tests/servicestests/res/xml/well_formed_metadata2.xml
@@ -0,0 +1,5 @@
+<module-metadata>
+ <module name="@string/module_1_name" packageName="com.android.module1" isHidden="false"
+ attribute1="attribute1" attribute2="attribute2" />
+ <module name="@string/module_2_name" packageName="com.android.module2" isHidden="true"/>
+</module-metadata>
diff --git a/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java b/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java
index 89c7b71e2cc1..93cac08f0033 100644
--- a/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java
@@ -20,6 +20,7 @@ import static com.android.server.am.MemoryStatUtil.BYTES_IN_KILOBYTE;
import static com.android.server.am.MemoryStatUtil.JIFFY_NANOS;
import static com.android.server.am.MemoryStatUtil.MemoryStat;
import static com.android.server.am.MemoryStatUtil.PAGE_SIZE;
+import static com.android.server.am.MemoryStatUtil.parseCmdlineFromProcfs;
import static com.android.server.am.MemoryStatUtil.parseMemoryStatFromMemcg;
import static com.android.server.am.MemoryStatUtil.parseMemoryStatFromProcfs;
import static com.android.server.am.MemoryStatUtil.parseVmHWMFromProcfs;
@@ -31,6 +32,7 @@ import androidx.test.filters.SmallTest;
import org.junit.Test;
+import java.io.ByteArrayOutputStream;
import java.util.Collections;
/**
@@ -232,4 +234,41 @@ public class MemoryStatUtilTest {
assertEquals(0, parseVmHWMFromProcfs(null));
}
+
+ @Test
+ public void testParseCmdlineFromProcfs_invalidValue() {
+ byte[] nothing = new byte[] {0x00, 0x74, 0x65, 0x73, 0x74}; // \0test
+
+ assertEquals("", parseCmdlineFromProcfs(bytesToString(nothing)));
+ }
+
+ @Test
+ public void testParseCmdlineFromProcfs_correctValue_noNullBytes() {
+ assertEquals("com.google.app", parseCmdlineFromProcfs("com.google.app"));
+ }
+
+ @Test
+ public void testParseCmdlineFromProcfs_correctValue_withNullBytes() {
+ byte[] trailing = new byte[] {0x74, 0x65, 0x73, 0x74, 0x00, 0x00, 0x00}; // test\0\0\0
+
+ assertEquals("test", parseCmdlineFromProcfs(bytesToString(trailing)));
+
+ // test\0\0test
+ byte[] inside = new byte[] {0x74, 0x65, 0x73, 0x74, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74};
+
+ assertEquals("test", parseCmdlineFromProcfs(bytesToString(trailing)));
+ }
+
+ @Test
+ public void testParseCmdlineFromProcfs_emptyContents() {
+ assertEquals("", parseCmdlineFromProcfs(""));
+
+ assertEquals("", parseCmdlineFromProcfs(null));
+ }
+
+ private static String bytesToString(byte[] bytes) {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ output.write(bytes, 0, bytes.length);
+ return output.toString();
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
index 751ed9b1bd15..d7a398e50a66 100644
--- a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
@@ -487,8 +487,8 @@ public class TrampolineTest {
@Test
public void adbBackup_calledBeforeInitialize_ignored() throws RemoteException {
- mTrampoline.adbBackup(mParcelFileDescriptorMock, true, true, true, true, true, true, true,
- true,
+ mTrampoline.adbBackup(mUserId, mParcelFileDescriptorMock, true, true,
+ true, true, true, true, true, true,
PACKAGE_NAMES);
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@@ -496,12 +496,11 @@ public class TrampolineTest {
@Test
public void adbBackup_forwarded() throws RemoteException {
mTrampoline.initializeService(UserHandle.USER_SYSTEM);
- mTrampoline.adbBackup(mParcelFileDescriptorMock, true, true, true, true, true, true, true,
- true,
+ mTrampoline.adbBackup(mUserId, mParcelFileDescriptorMock, true, true,
+ true, true, true, true, true, true,
PACKAGE_NAMES);
- verify(mBackupManagerServiceMock).adbBackup(mParcelFileDescriptorMock, true, true, true,
- true,
- true, true, true, true, PACKAGE_NAMES);
+ verify(mBackupManagerServiceMock).adbBackup(mUserId, mParcelFileDescriptorMock, true,
+ true, true, true, true, true, true, true, PACKAGE_NAMES);
}
@Test
@@ -519,15 +518,15 @@ public class TrampolineTest {
@Test
public void adbRestore_calledBeforeInitialize_ignored() throws RemoteException {
- mTrampoline.adbRestore(mParcelFileDescriptorMock);
+ mTrampoline.adbRestore(mUserId, mParcelFileDescriptorMock);
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
public void adbRestore_forwarded() throws RemoteException {
mTrampoline.initializeService(UserHandle.USER_SYSTEM);
- mTrampoline.adbRestore(mParcelFileDescriptorMock);
- verify(mBackupManagerServiceMock).adbRestore(mParcelFileDescriptorMock);
+ mTrampoline.adbRestore(mUserId, mParcelFileDescriptorMock);
+ verify(mBackupManagerServiceMock).adbRestore(mUserId, mParcelFileDescriptorMock);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyEventLoggerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyEventLoggerTest.java
new file mode 100644
index 000000000000..b24bca8fc050
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyEventLoggerTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.admin.DevicePolicyEventLogger;
+import android.content.ComponentName;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit tests for {@link DevicePolicyEventLogger}.
+ * <p/>
+ * Run with <code>atest DevicePolicyEventLoggerTest</code>.
+ */
+@RunWith(AndroidJUnit4.class)
+public class DevicePolicyEventLoggerTest {
+ @Test
+ public void testAllFields() {
+ final DevicePolicyEventLogger eventLogger = DevicePolicyEventLogger
+ .createEvent(5)
+ .setBoolean(true)
+ .setStrings("string1", "string2", "string3")
+ .setAdmin(new ComponentName("com.test.package", ".TestAdmin"))
+ .setInt(4321)
+ .setTimePeriod(1234L);
+ assertThat(eventLogger.getEventId()).isEqualTo(5);
+ assertThat(eventLogger.getBoolean()).isTrue();
+ assertThat(eventLogger.getStringArray())
+ .isEqualTo(new String[] {"string1", "string2", "string3"});
+ assertThat(eventLogger.getAdminPackageName()).isEqualTo("com.test.package");
+ assertThat(eventLogger.getInt()).isEqualTo(4321);
+ assertThat(eventLogger.getTimePeriod()).isEqualTo(1234L);
+ }
+
+ @Test
+ public void testStrings() {
+ assertThat(DevicePolicyEventLogger
+ .createEvent(0)
+ .setStrings("string1", "string2", "string3").getStringArray())
+ .isEqualTo(new String[] {"string1", "string2", "string3"});
+
+ assertThat(DevicePolicyEventLogger
+ .createEvent(0)
+ .setStrings("string1", new String[] {"string2", "string3"}).getStringArray())
+ .isEqualTo(new String[] {"string1", "string2", "string3"});
+
+ assertThat(DevicePolicyEventLogger
+ .createEvent(0)
+ .setStrings("string1", "string2", new String[] {"string3"}).getStringArray())
+ .isEqualTo(new String[] {"string1", "string2", "string3"});
+
+ assertThat(DevicePolicyEventLogger
+ .createEvent(0)
+ .setStrings((String) null).getStringArray())
+ .isEqualTo(new String[] {null});
+
+ assertThat(DevicePolicyEventLogger
+ .createEvent(0)
+ .setStrings((String[]) null).getStringArray())
+ .isEqualTo(null);
+
+ assertThrows(NullPointerException.class, () -> DevicePolicyEventLogger
+ .createEvent(0)
+ .setStrings("string1", "string2", null));
+ }
+
+ @Test
+ public void testAdmins() {
+ assertThat(DevicePolicyEventLogger
+ .createEvent(0)
+ .setAdmin("com.package.name")
+ .getAdminPackageName())
+ .isEqualTo("com.package.name");
+
+ assertThat(DevicePolicyEventLogger
+ .createEvent(0)
+ .setAdmin(new ComponentName("com.package.name", ".TestAdmin"))
+ .getAdminPackageName())
+ .isEqualTo("com.package.name");
+ }
+
+ @Test
+ public void testDefaultValues() {
+ final DevicePolicyEventLogger eventLogger = DevicePolicyEventLogger
+ .createEvent(0);
+ assertThat(eventLogger.getEventId()).isEqualTo(0);
+ assertThat(eventLogger.getBoolean()).isFalse();
+ assertThat(eventLogger.getStringArray()).isEqualTo(null);
+ assertThat(eventLogger.getAdminPackageName()).isEqualTo(null);
+ assertThat(eventLogger.getInt()).isEqualTo(0);
+ assertThat(eventLogger.getTimePeriod()).isEqualTo(0L);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 729fac5b1dff..38e8ac2d8f4c 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -72,7 +72,6 @@ import android.content.pm.StringParceledListSlice;
import android.content.pm.UserInfo;
import android.graphics.Color;
import android.net.Uri;
-import android.net.wifi.WifiInfo;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Process;
@@ -1989,17 +1988,16 @@ public class DevicePolicyManagerTest extends DpmTestBase {
// Test 4, Caller is DO now.
assertTrue(dpm.setDeviceOwner(admin1, null, UserHandle.USER_SYSTEM));
- // 4-1. But no WifiInfo.
+ // 4-1. But WifiManager is not ready.
assertNull(dpm.getWifiMacAddress(admin1));
- // 4-2. Returns WifiInfo, but with the default MAC.
- when(getServices().wifiManager.getConnectionInfo()).thenReturn(new WifiInfo());
+ // 4-2. When WifiManager returns an empty array, dpm should also output null.
+ when(getServices().wifiManager.getFactoryMacAddresses()).thenReturn(new String[0]);
assertNull(dpm.getWifiMacAddress(admin1));
// 4-3. With a real MAC address.
- final WifiInfo wi = new WifiInfo();
- wi.setMacAddress("11:22:33:44:55:66");
- when(getServices().wifiManager.getConnectionInfo()).thenReturn(wi);
+ final String[] macAddresses = new String[]{"11:22:33:44:55:66"};
+ when(getServices().wifiManager.getFactoryMacAddresses()).thenReturn(macAddresses);
assertEquals("11:22:33:44:55:66", dpm.getWifiMacAddress(admin1));
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java b/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java
new file mode 100644
index 000000000000..bd3d9ab2220d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.pm;
+
+import android.content.Context;
+import android.content.pm.ModuleInfo;
+import android.test.InstrumentationTestCase;
+
+import com.android.frameworks.servicestests.R;
+
+import java.util.Collections;
+import java.util.List;
+
+public class ModuleInfoProviderTest extends InstrumentationTestCase {
+ public void testSuccessfulParse() {
+ ModuleInfoProvider provider = getProvider(R.xml.well_formed_metadata);
+
+ List<ModuleInfo> mi = provider.getInstalledModules(0);
+ assertEquals(2, mi.size());
+
+ Collections.sort(mi, (ModuleInfo m1, ModuleInfo m2) ->
+ m1.getPackageName().compareTo(m1.getPackageName()));
+ assertEquals("com.android.module1", mi.get(0).getPackageName());
+ assertEquals("com.android.module2", mi.get(1).getPackageName());
+
+ ModuleInfo mi1 = provider.getModuleInfo("com.android.module1", 0);
+ assertEquals("com.android.module1", mi1.getPackageName());
+ assertEquals("module_1_name", mi1.getName());
+ assertEquals(false, mi1.isHidden());
+
+ ModuleInfo mi2 = provider.getModuleInfo("com.android.module2", 0);
+ assertEquals("com.android.module2", mi2.getPackageName());
+ assertEquals("module_2_name", mi2.getName());
+ assertEquals(true, mi2.isHidden());
+ }
+
+ public void testParseFailure_incorrectTopLevelElement() {
+ ModuleInfoProvider provider = getProvider(R.xml.unparseable_metadata1);
+ assertEquals(0, provider.getInstalledModules(0).size());
+ }
+
+ public void testParseFailure_incorrectModuleElement() {
+ ModuleInfoProvider provider = getProvider(R.xml.unparseable_metadata2);
+ assertEquals(0, provider.getInstalledModules(0).size());
+ }
+
+ public void testParse_unknownAttributesIgnored() {
+ ModuleInfoProvider provider = getProvider(R.xml.well_formed_metadata);
+
+ List<ModuleInfo> mi = provider.getInstalledModules(0);
+ assertEquals(2, mi.size());
+
+ ModuleInfo mi1 = provider.getModuleInfo("com.android.module1", 0);
+ assertEquals("com.android.module1", mi1.getPackageName());
+ assertEquals("module_1_name", mi1.getName());
+ assertEquals(false, mi1.isHidden());
+ }
+
+ /**
+ * Constructs an {@code ModuleInfoProvider} using the test package resources.
+ */
+ private ModuleInfoProvider getProvider(int resourceId) {
+ final Context ctx = getInstrumentation().getContext();
+ return new ModuleInfoProvider(ctx.getResources().getXml(resourceId), ctx.getResources());
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
index 41d5a1c2fac4..afbe6bc21d0d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -27,6 +27,8 @@ import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
@@ -417,7 +419,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
verifyLights();
assertTrue(r.isInterruptive());
- assertFalse(r.getAudiblyAlerted());
+ assertEquals(-1, r.getLastAudiblyAlertedMs());
}
@Test
@@ -430,7 +432,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
verifyNeverVibrate();
verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt());
assertTrue(r.isInterruptive());
- assertTrue(r.getAudiblyAlerted());
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
}
@Test
@@ -441,7 +443,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
verifyBeep();
assertTrue(r.isInterruptive());
- assertTrue(r.getAudiblyAlerted());
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
}
@Test
@@ -452,7 +454,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
verifyNeverBeep();
assertFalse(r.isInterruptive());
- assertFalse(r.getAudiblyAlerted());
+ assertEquals(-1, r.getLastAudiblyAlertedMs());
}
@Test
@@ -490,7 +492,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
verifyNeverBeep();
verifyNeverVibrate();
assertFalse(r.isInterruptive());
- assertFalse(r.getAudiblyAlerted());
+ assertEquals(-1, r.getLastAudiblyAlertedMs());
}
@Test
@@ -503,7 +505,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
verifyNeverBeep();
verifyNeverVibrate();
assertFalse(r.isInterruptive());
- assertFalse(r.getAudiblyAlerted());
+ assertEquals(-1, r.getLastAudiblyAlertedMs());
}
@Test
@@ -520,7 +522,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
verifyBeepLooped();
verify(mAccessibilityService, times(2)).sendAccessibilityEvent(any(), anyInt());
assertTrue(r.isInterruptive());
- assertTrue(r.getAudiblyAlerted());
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
}
@Test
@@ -549,7 +551,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
verifyNeverStopAudio();
assertTrue(r.isInterruptive());
- assertTrue(r.getAudiblyAlerted());
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
}
@Test
@@ -563,9 +565,9 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
verifyNeverStopAudio();
assertTrue(r.isInterruptive());
- assertTrue(r.getAudiblyAlerted());
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
assertFalse(s.isInterruptive());
- assertFalse(s.getAudiblyAlerted());
+ assertEquals(-1, s.getLastAudiblyAlertedMs());
}
/**
@@ -602,7 +604,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
mService.buzzBeepBlinkLocked(s); // this no longer owns the stream
verifyNeverStopAudio();
assertTrue(other.isInterruptive());
- assertTrue(other.getAudiblyAlerted());
+ assertNotEquals(-1, other.getLastAudiblyAlertedMs());
}
@Test
@@ -628,14 +630,14 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
// set up internal state
mService.buzzBeepBlinkLocked(r);
assertTrue(r.isInterruptive());
- assertTrue(r.getAudiblyAlerted());
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
Mockito.reset(mRingtonePlayer);
// quiet update should stop making noise
mService.buzzBeepBlinkLocked(s);
verifyStopAudio();
assertFalse(s.isInterruptive());
- assertFalse(s.getAudiblyAlerted());
+ assertEquals(-1, s.getLastAudiblyAlertedMs());
}
@Test
@@ -647,14 +649,14 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
// set up internal state
mService.buzzBeepBlinkLocked(r);
assertTrue(r.isInterruptive());
- assertTrue(r.getAudiblyAlerted());
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
Mockito.reset(mRingtonePlayer);
// stop making noise - this is a weird corner case, but quiet should override once
mService.buzzBeepBlinkLocked(s);
verifyStopAudio();
assertFalse(s.isInterruptive());
- assertFalse(s.getAudiblyAlerted());
+ assertEquals(-1, s.getLastAudiblyAlertedMs());
}
@Test
@@ -671,7 +673,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
verify(mService, times(1)).playInCallNotification();
verifyNeverBeep(); // doesn't play normal beep
assertTrue(r.isInterruptive());
- assertTrue(r.getAudiblyAlerted());
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
}
@Test
@@ -691,7 +693,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
eq(effect), anyString(),
(AudioAttributes) anyObject());
assertTrue(r.isInterruptive());
- assertTrue(r.getAudiblyAlerted());
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
}
@Test
@@ -709,7 +711,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
verifyNeverVibrate();
verifyBeepLooped();
assertTrue(r.isInterruptive());
- assertTrue(r.getAudiblyAlerted());
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
}
@Test
@@ -729,7 +731,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
verify(mRingtonePlayer, never()).playAsync
(anyObject(), anyObject(), anyBoolean(), anyObject());
assertTrue(r.isInterruptive());
- assertTrue(r.getAudiblyAlerted());
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
}
@Test
@@ -746,7 +748,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
verifyDelayedVibrateLooped();
assertTrue(r.isInterruptive());
- assertTrue(r.getAudiblyAlerted());
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
}
@Test
@@ -758,7 +760,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
verifyNeverBeep();
verifyVibrate();
assertTrue(r.isInterruptive());
- assertTrue(r.getAudiblyAlerted());
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
}
@Test
@@ -768,7 +770,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
mService.buzzBeepBlinkLocked(r);
verifyVibrateLooped();
assertTrue(r.isInterruptive());
- assertTrue(r.getAudiblyAlerted());
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
}
@Test
@@ -784,7 +786,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
mService.buzzBeepBlinkLocked(r);
verifyVibrate();
assertTrue(r.isInterruptive());
- assertTrue(r.getAudiblyAlerted());
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
}
@Test
@@ -795,7 +797,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
verifyNeverBeep();
assertFalse(child.isInterruptive());
- assertFalse(child.getAudiblyAlerted());
+ assertEquals(-1, child.getLastAudiblyAlertedMs());
}
@Test
@@ -808,7 +810,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
verifyBeepLooped();
// summaries are never interruptive for notification counts
assertFalse(summary.isInterruptive());
- assertTrue(summary.getAudiblyAlerted());
+ assertNotEquals(-1, summary.getLastAudiblyAlertedMs());
}
@Test
@@ -819,7 +821,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
verifyBeepLooped();
assertTrue(nonGroup.isInterruptive());
- assertTrue(nonGroup.getAudiblyAlerted());
+ assertNotEquals(-1, nonGroup.getLastAudiblyAlertedMs());
}
@Test
@@ -831,7 +833,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
verifyNeverBeep();
assertFalse(summary.isInterruptive());
- assertFalse(summary.getAudiblyAlerted());
+ assertEquals(-1, summary.getLastAudiblyAlertedMs());
}
@Test
@@ -842,7 +844,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
verifyBeepLooped();
assertTrue(child.isInterruptive());
- assertTrue(child.getAudiblyAlerted());
+ assertNotEquals(-1, child.getLastAudiblyAlertedMs());
}
@Test
@@ -853,7 +855,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
verifyBeepLooped();
assertTrue(nonGroup.isInterruptive());
- assertTrue(nonGroup.getAudiblyAlerted());
+ assertNotEquals(-1, nonGroup.getLastAudiblyAlertedMs());
}
@Test
@@ -864,7 +866,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
verifyBeepLooped();
assertTrue(group.isInterruptive());
- assertTrue(group.getAudiblyAlerted());
+ assertNotEquals(-1, group.getLastAudiblyAlertedMs());
}
@Test
@@ -877,13 +879,13 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
mService.buzzBeepBlinkLocked(r);
Mockito.reset(mVibrator);
assertTrue(r.isInterruptive());
- assertTrue(r.getAudiblyAlerted());
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
// update should not beep
mService.buzzBeepBlinkLocked(s);
verifyNeverVibrate();
assertFalse(s.isInterruptive());
- assertFalse(s.getAudiblyAlerted());
+ assertEquals(-1, s.getLastAudiblyAlertedMs());
}
@Test
@@ -896,7 +898,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
verifyNeverStopVibrate();
assertTrue(r.isInterruptive());
- assertTrue(r.getAudiblyAlerted());
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
}
@Test
@@ -910,9 +912,9 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
verifyNeverStopVibrate();
assertTrue(r.isInterruptive());
- assertTrue(r.getAudiblyAlerted());
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
assertFalse(s.isInterruptive());
- assertFalse(s.getAudiblyAlerted());
+ assertEquals(-1, s.getLastAudiblyAlertedMs());
}
@Test
@@ -931,11 +933,11 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
mService.buzzBeepBlinkLocked(s); // this no longer owns the stream
verifyNeverStopVibrate();
assertTrue(r.isInterruptive());
- assertTrue(r.getAudiblyAlerted());
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
assertTrue(other.isInterruptive());
- assertTrue(other.getAudiblyAlerted());
+ assertNotEquals(-1, other.getLastAudiblyAlertedMs());
assertFalse(s.isInterruptive());
- assertFalse(s.getAudiblyAlerted());
+ assertEquals(-1, s.getLastAudiblyAlertedMs());
}
@Test
@@ -951,7 +953,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
mService.buzzBeepBlinkLocked(other);
verifyNeverStopVibrate();
assertFalse(other.isInterruptive());
- assertFalse(other.getAudiblyAlerted());
+ assertEquals(-1, other.getLastAudiblyAlertedMs());
}
@Test
@@ -968,9 +970,9 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
mService.buzzBeepBlinkLocked(s);
verifyStopVibrate();
assertTrue(r.isInterruptive());
- assertTrue(r.getAudiblyAlerted());
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
assertFalse(s.isInterruptive());
- assertFalse(s.getAudiblyAlerted());
+ assertEquals(-1, s.getLastAudiblyAlertedMs());
}
@Test
@@ -987,9 +989,9 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
mService.buzzBeepBlinkLocked(s);
verifyStopVibrate();
assertTrue(r.isInterruptive());
- assertTrue(r.getAudiblyAlerted());
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
assertFalse(s.isInterruptive());
- assertFalse(s.getAudiblyAlerted());
+ assertEquals(-1, s.getLastAudiblyAlertedMs());
}
@Test
@@ -1007,9 +1009,9 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
mService.buzzBeepBlinkLocked(s);
verifyStopVibrate();
assertTrue(r.isInterruptive());
- assertTrue(r.getAudiblyAlerted());
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
assertFalse(s.isInterruptive());
- assertFalse(s.getAudiblyAlerted());
+ assertEquals(-1, s.getLastAudiblyAlertedMs());
}
@Test
@@ -1027,7 +1029,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
mService.buzzBeepBlinkLocked(r);
verifyNeverBeep();
assertFalse(r.isInterruptive());
- assertFalse(r.getAudiblyAlerted());
+ assertEquals(-1, r.getLastAudiblyAlertedMs());
}
@Test
@@ -1039,7 +1041,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
mService.buzzBeepBlinkLocked(r);
verifyNeverBeep();
assertFalse(r.isInterruptive());
- assertFalse(r.getAudiblyAlerted());
+ assertEquals(-1, r.getLastAudiblyAlertedMs());
}
@Test
@@ -1082,7 +1084,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
mService.buzzBeepBlinkLocked(r);
verifyNeverBeep();
assertFalse(r.isInterruptive());
- assertFalse(r.getAudiblyAlerted());
+ assertEquals(-1, r.getLastAudiblyAlertedMs());
}
@Test
@@ -1116,7 +1118,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
mService.buzzBeepBlinkLocked(r);
verifyNeverLights();
assertFalse(r.isInterruptive());
- assertFalse(r.getAudiblyAlerted());
+ assertEquals(-1, r.getLastAudiblyAlertedMs());
}
@Test
@@ -1126,7 +1128,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
mService.buzzBeepBlinkLocked(r);
verifyNeverLights();
assertFalse(r.isInterruptive());
- assertFalse(r.getAudiblyAlerted());
+ assertEquals(-1, r.getLastAudiblyAlertedMs());
}
@Test
@@ -1135,7 +1137,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
mService.buzzBeepBlinkLocked(r);
verifyLights();
assertTrue(r.isInterruptive());
- assertFalse(r.getAudiblyAlerted());
+ assertEquals(-1, r.getLastAudiblyAlertedMs());
r = getLightsOnceNotification();
r.isUpdate = true;
@@ -1143,7 +1145,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
// checks that lights happened once, i.e. this new call didn't trigger them again
verifyLights();
assertFalse(r.isInterruptive());
- assertFalse(r.getAudiblyAlerted());
+ assertEquals(-1, r.getLastAudiblyAlertedMs());
}
@Test
@@ -1153,7 +1155,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
mService.buzzBeepBlinkLocked(r);
verifyNeverLights();
assertFalse(r.isInterruptive());
- assertFalse(r.getAudiblyAlerted());
+ assertEquals(-1, r.getLastAudiblyAlertedMs());
}
@Test
@@ -1162,7 +1164,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
mService.buzzBeepBlinkLocked(r);
verifyNeverLights();
assertFalse(r.isInterruptive());
- assertFalse(r.getAudiblyAlerted());
+ assertEquals(-1, r.getLastAudiblyAlertedMs());
}
@Test
@@ -1172,7 +1174,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
mService.buzzBeepBlinkLocked(r);
verifyNeverLights();
assertFalse(r.isInterruptive());
- assertFalse(r.getAudiblyAlerted());
+ assertEquals(-1, r.getLastAudiblyAlertedMs());
}
@Test
@@ -1182,7 +1184,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
mService.buzzBeepBlinkLocked(r);
verifyNeverLights();
assertFalse(r.isInterruptive());
- assertFalse(r.getAudiblyAlerted());
+ assertEquals(-1, r.getLastAudiblyAlertedMs());
}
@Test
@@ -1192,7 +1194,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
mService.buzzBeepBlinkLocked(r);
verifyNeverLights();
assertFalse(r.isInterruptive());
- assertFalse(r.getAudiblyAlerted());
+ assertEquals(-1, r.getLastAudiblyAlertedMs());
}
@Test
@@ -1203,7 +1205,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
verifyNeverLights();
assertFalse(child.isInterruptive());
- assertFalse(child.getAudiblyAlerted());
+ assertEquals(-1, child.getLastAudiblyAlertedMs());
}
@Test
@@ -1216,7 +1218,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
verifyLights();
// summaries should never count for interruptiveness counts
assertFalse(summary.isInterruptive());
- assertFalse(summary.getAudiblyAlerted());
+ assertEquals(-1, summary.getLastAudiblyAlertedMs());
}
@Test
@@ -1227,7 +1229,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
verifyLights();
assertTrue(nonGroup.isInterruptive());
- assertFalse(nonGroup.getAudiblyAlerted());
+ assertEquals(-1, nonGroup.getLastAudiblyAlertedMs());
}
@Test
@@ -1239,7 +1241,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
verifyNeverLights();
assertFalse(summary.isInterruptive());
- assertFalse(summary.getAudiblyAlerted());
+ assertEquals(-1, summary.getLastAudiblyAlertedMs());
}
@Test
@@ -1250,7 +1252,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
verifyLights();
assertTrue(child.isInterruptive());
- assertFalse(child.getAudiblyAlerted());
+ assertEquals(-1, child.getLastAudiblyAlertedMs());
}
@Test
@@ -1261,7 +1263,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
verifyLights();
assertTrue(nonGroup.isInterruptive());
- assertFalse(nonGroup.getAudiblyAlerted());
+ assertEquals(-1, nonGroup.getLastAudiblyAlertedMs());
}
@Test
@@ -1272,7 +1274,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
verifyLights();
assertTrue(group.isInterruptive());
- assertFalse(group.getAudiblyAlerted());
+ assertEquals(-1, group.getLastAudiblyAlertedMs());
}
static class VibrateRepeatMatcher implements ArgumentMatcher<VibrationEffect> {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index bcba15df8756..daca9cb28051 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -16,12 +16,9 @@
package com.android.server.notification;
-import static android.service.notification.NotificationListenerService.Ranking
- .USER_SENTIMENT_NEGATIVE;
-import static android.service.notification.NotificationListenerService.Ranking
- .USER_SENTIMENT_NEUTRAL;
-import static android.service.notification.NotificationListenerService.Ranking
- .USER_SENTIMENT_POSITIVE;
+import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
+import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
+import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -92,7 +89,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
assertEquals(getShowBadge(i), ranking.canShowBadge());
assertEquals(getUserSentiment(i), ranking.getUserSentiment());
assertEquals(getHidden(i), ranking.isSuspended());
- assertEquals(audiblyAlerted(i), ranking.audiblyAlerted());
+ assertEquals(lastAudiblyAlerted(i), ranking.getLastAudiblyAlertedMillis());
assertActionsEqual(getSmartActions(key, i), ranking.getSmartActions());
assertEquals(getSmartReplies(key, i), ranking.getSmartReplies());
}
@@ -113,7 +110,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
Bundle mHidden = new Bundle();
Bundle smartActions = new Bundle();
Bundle smartReplies = new Bundle();
- Bundle audiblyAlerted = new Bundle();
+ Bundle lastAudiblyAlerted = new Bundle();
Bundle noisy = new Bundle();
for (int i = 0; i < mKeys.length; i++) {
@@ -134,14 +131,14 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
mHidden.putBoolean(key, getHidden(i));
smartActions.putParcelableArrayList(key, getSmartActions(key, i));
smartReplies.putCharSequenceArrayList(key, getSmartReplies(key, i));
- audiblyAlerted.putBoolean(key, audiblyAlerted(i));
+ lastAudiblyAlerted.putLong(key, lastAudiblyAlerted(i));
noisy.putBoolean(key, getNoisy(i));
}
NotificationRankingUpdate update = new NotificationRankingUpdate(mKeys,
interceptedKeys.toArray(new String[0]), visibilityOverrides,
suppressedVisualEffects, importance, explanation, overrideGroupKeys,
channels, overridePeople, snoozeCriteria, showBadge, userSentiment, mHidden,
- smartActions, smartReplies, audiblyAlerted, noisy);
+ smartActions, smartReplies, lastAudiblyAlerted, noisy);
return update;
}
@@ -193,8 +190,8 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
return index % 2 == 0;
}
- private boolean audiblyAlerted(int index) {
- return index < 2;
+ private long lastAudiblyAlerted(int index) {
+ return index * 2000;
}
private boolean getNoisy(int index) {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index 0eeeeed0b227..65e640f5f73c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -19,12 +19,9 @@ import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
-import static android.service.notification.NotificationListenerService.Ranking
- .USER_SENTIMENT_NEGATIVE;
-import static android.service.notification.NotificationListenerService.Ranking
- .USER_SENTIMENT_NEUTRAL;
-import static android.service.notification.NotificationListenerService.Ranking
- .USER_SENTIMENT_POSITIVE;
+import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
+import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
+import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@@ -806,6 +803,18 @@ public class NotificationRecordTest extends UiServiceTestCase {
}
@Test
+ public void testSetDidNotAudiblyAlert() {
+ StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
+ true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+ false /* lights */, false /* defaultLights */, groupId /* group */);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+ record.setAudiblyAlerted(false);
+
+ assertEquals(-1, record.getLastAudiblyAlertedMs());
+ }
+
+ @Test
public void testSetAudiblyAlerted() {
StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
@@ -814,6 +823,6 @@ public class NotificationRecordTest extends UiServiceTestCase {
record.setAudiblyAlerted(true);
- assertTrue(record.getAudiblyAlerted());
+ assertNotEquals(-1, record.getLastAudiblyAlertedMs());
}
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
index 9bd3f263a277..68d3e4c3f704 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -17,20 +17,33 @@
package com.android.server.notification;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertNull;
import android.app.NotificationManager.Policy;
+import android.content.ComponentName;
import android.net.Uri;
+import android.provider.Settings;
+import android.service.notification.Condition;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig.EventInfo;
import android.service.notification.ZenPolicy;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Xml;
+import com.android.internal.util.FastXmlSerializer;
import com.android.server.UiServiceTestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -138,6 +151,54 @@ public class ZenModeConfigTest extends UiServiceTestCase {
assertEquals(event, eventParsed);
}
+ @Test
+ public void testRuleXml() throws Exception {
+ String tag = "tag";
+
+ ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
+ rule.configurationActivity = new ComponentName("a", "a");
+ rule.component = new ComponentName("a", "b");
+ rule.conditionId = new Uri.Builder().scheme("hello").build();
+ rule.condition = new Condition(rule.conditionId, "", Condition.STATE_TRUE);
+ rule.enabled = true;
+ rule.creationTime = 123;
+ rule.id = "id";
+ rule.zenMode = Settings.Global.ZEN_MODE_ALARMS;
+ rule.modified = true;
+ rule.name = "name";
+ rule.snoozing = true;
+
+ XmlSerializer out = new FastXmlSerializer();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ out.setOutput(new BufferedOutputStream(baos), "utf-8");
+ out.startDocument(null, true);
+ out.startTag(null, tag);
+ ZenModeConfig.writeRuleXml(rule, out);
+ out.endTag(null, tag);
+ out.endDocument();
+
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new BufferedInputStream(
+ new ByteArrayInputStream(baos.toByteArray())), null);
+ parser.nextTag();
+ ZenModeConfig.ZenRule fromXml = ZenModeConfig.readRuleXml(parser);
+ // read from backing component
+ assertEquals("a", fromXml.pkg);
+ // always resets on reboot
+ assertFalse(fromXml.snoozing);
+ //should all match original
+ assertEquals(rule.component, fromXml.component);
+ assertEquals(rule.configurationActivity, fromXml.configurationActivity);
+ assertNull(fromXml.enabler);
+ assertEquals(rule.condition, fromXml.condition);
+ assertEquals(rule.enabled, fromXml.enabled);
+ assertEquals(rule.creationTime, fromXml.creationTime);
+ assertEquals(rule.modified, fromXml.modified);
+ assertEquals(rule.conditionId, fromXml.conditionId);
+ assertEquals(rule.name, fromXml.name);
+ assertEquals(rule.zenMode, fromXml.zenMode);
+ }
+
private ZenModeConfig getMutedNotificationsConfig() {
ZenModeConfig config = new ZenModeConfig();
// Allow alarms, media, and system
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 6c7ede3df4db..dc3287e690a8 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -633,8 +633,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
mZenModeHelperSpy.mConfig.manualRule.zenMode =
Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
mZenModeHelperSpy.mConfig.manualRule.component = new ComponentName("a", "a");
+ mZenModeHelperSpy.mConfig.manualRule.pkg = "a";
mZenModeHelperSpy.mConfig.manualRule.enabled = true;
- mZenModeHelperSpy.mConfig.manualRule.snoozing = true;
ZenModeConfig expected = mZenModeHelperSpy.mConfig.copy();
@@ -645,7 +645,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
parser.nextTag();
mZenModeHelperSpy.readXml(parser, false);
- assertEquals(expected, mZenModeHelperSpy.mConfig);
+ assertEquals("Config mismatch: current vs expected: "
+ + mZenModeHelperSpy.mConfig.diff(expected), expected, mZenModeHelperSpy.mConfig);
}
@Test
@@ -662,7 +663,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
customRule.name = "Custom Rule";
customRule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
customRule.conditionId = ZenModeConfig.toScheduleConditionId(customRuleInfo);
- customRule.component = new ComponentName("android", "ScheduleConditionProvider");
+ customRule.configurationActivity
+ = new ComponentName("android", "ScheduleConditionProvider");
+ customRule.pkg = customRule.configurationActivity.getPackageName();
automaticRules.put("customRule", customRule);
mZenModeHelperSpy.mConfig.automaticRules = automaticRules;
@@ -674,7 +677,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
new ByteArrayInputStream(baos.toByteArray())), null);
parser.nextTag();
mZenModeHelperSpy.readXml(parser, true);
- assertEquals(original, mZenModeHelperSpy.mConfig);
+ assertEquals("Config mismatch: current vs original: "
+ + mZenModeHelperSpy.mConfig.diff(original), original, mZenModeHelperSpy.mConfig);
assertEquals(original.hashCode(), mZenModeHelperSpy.mConfig.hashCode());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index 85410f5e14df..b2a28699e6a0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -797,7 +797,7 @@ public class ActivityStackTests extends ActivityTestsBase {
public boolean mChanged = false;
@Override
- public void onStackOrderChanged() {
+ public void onStackOrderChanged(ActivityStack stack) {
mChanged = true;
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 7c43cf3fba83..61e968d6da00 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -414,10 +414,10 @@ public class ActivityStarterTests extends ActivityTestsBase {
.setActivityOptions(new SafeActivityOptions(options))
.execute();
- // verify that values are passed to the modifier. Values are passed twice -- once for
+ // verify that values are passed to the modifier. Values are passed thrice -- two for
// setting initial state, another when task is created.
- verify(modifier, times(2)).onCalculate(any(), eq(windowLayout), any(), any(), eq(options),
- any(), any());
+ verify(modifier, times(3)).onCalculate(any(), eq(windowLayout), any(), any(), eq(options),
+ anyInt(), any(), any());
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 7c83ecc22c90..8430616731d2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -19,6 +19,8 @@ package com.android.server.wm;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.os.Build.VERSION_CODES.P;
+import static android.os.Build.VERSION_CODES.Q;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT;
import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
@@ -309,9 +311,27 @@ public class DisplayContentTests extends WindowTestsBase {
@Test
public void testFocusedWindowMultipleDisplays() {
+ doTestFocusedWindowMultipleDisplays(false /* perDisplayFocusEnabled */, Q);
+ }
+
+ @Test
+ public void testFocusedWindowMultipleDisplaysPerDisplayFocusEnabled() {
+ doTestFocusedWindowMultipleDisplays(true /* perDisplayFocusEnabled */, Q);
+ }
+
+ @Test
+ public void testFocusedWindowMultipleDisplaysPerDisplayFocusEnabledLegacyApp() {
+ doTestFocusedWindowMultipleDisplays(true /* perDisplayFocusEnabled */, P);
+ }
+
+ private void doTestFocusedWindowMultipleDisplays(boolean perDisplayFocusEnabled,
+ int targetSdk) {
+ mWm.mPerDisplayFocusEnabled = perDisplayFocusEnabled;
+
// Create a focusable window and check that focus is calculated correctly
final WindowState window1 =
createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "window1");
+ window1.mAppToken.mTargetSdk = targetSdk;
updateFocusedWindow();
assertTrue(window1.isFocused());
assertEquals(window1, mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus);
@@ -324,16 +344,17 @@ public class DisplayContentTests extends WindowTestsBase {
// Add a window to the second display, and it should be focused
final WindowState window2 = createWindow(null, TYPE_BASE_APPLICATION, dc, "window2");
+ window2.mAppToken.mTargetSdk = targetSdk;
updateFocusedWindow();
- assertTrue(window1.isFocused());
assertTrue(window2.isFocused());
+ assertEquals(perDisplayFocusEnabled && targetSdk >= Q, window1.isFocused());
assertEquals(window2, mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus);
- // Move the first window to the to including parents, and make sure focus is updated
+ // Move the first window to top including parents, and make sure focus is updated
window1.getParent().positionChildAt(POSITION_TOP, window1, true);
updateFocusedWindow();
assertTrue(window1.isFocused());
- assertTrue(window2.isFocused());
+ assertEquals(perDisplayFocusEnabled && targetSdk >= Q, window2.isFocused());
assertEquals(window1, mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java
index 3720c8566e74..8c3dec7f1e75 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java
@@ -31,6 +31,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_DONE;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP;
@@ -89,10 +90,10 @@ public class LaunchParamsControllerTests extends ActivityTestsBase {
final WindowLayout layout = new WindowLayout(0, 0, 0, 0, 0, 0, 0);
final ActivityOptions options = mock(ActivityOptions.class);
- mController.calculate(record.getTaskRecord(), layout, record, source, options,
+ mController.calculate(record.getTaskRecord(), layout, record, source, options, PHASE_BOUNDS,
new LaunchParams());
verify(positioner, times(1)).onCalculate(eq(record.getTaskRecord()), eq(layout), eq(record),
- eq(source), eq(options), any(), any());
+ eq(source), eq(options), anyInt(), any(), any());
}
/**
@@ -115,9 +116,9 @@ public class LaunchParamsControllerTests extends ActivityTestsBase {
mPersister.putLaunchParams(userId, name, expected);
mController.calculate(activity.getTaskRecord(), null /*layout*/, activity, null /*source*/,
- null /*options*/, new LaunchParams());
- verify(positioner, times(1)).onCalculate(any(), any(), any(), any(), any(), eq(expected),
- any());
+ null /*options*/, PHASE_BOUNDS, new LaunchParams());
+ verify(positioner, times(1)).onCalculate(any(), any(), any(), any(), any(), anyInt(),
+ eq(expected), any());
}
/**
@@ -128,14 +129,15 @@ public class LaunchParamsControllerTests extends ActivityTestsBase {
final LaunchParamsModifier
ignoredPositioner = mock(LaunchParamsModifier.class);
final LaunchParamsModifier earlyExitPositioner =
- (task, layout, activity, source, options, currentParams, outParams) -> RESULT_DONE;
+ (task, layout, activity, source, options, phase, currentParams, outParams)
+ -> RESULT_DONE;
mController.registerModifier(ignoredPositioner);
mController.registerModifier(earlyExitPositioner);
mController.calculate(null /*task*/, null /*layout*/, null /*activity*/,
- null /*source*/, null /*options*/, new LaunchParams());
- verify(ignoredPositioner, never()).onCalculate(any(), any(), any(), any(), any(),
+ null /*source*/, null /*options*/, PHASE_BOUNDS, new LaunchParams());
+ verify(ignoredPositioner, never()).onCalculate(any(), any(), any(), any(), any(), anyInt(),
any(), any());
}
@@ -152,20 +154,20 @@ public class LaunchParamsControllerTests extends ActivityTestsBase {
mController.registerModifier(firstPositioner);
mController.calculate(null /*task*/, null /*layout*/, null /*activity*/,
- null /*source*/, null /*options*/, new LaunchParams());
- verify(firstPositioner, times(1)).onCalculate(any(), any(), any(), any(), any(), any(),
- any());
+ null /*source*/, null /*options*/, PHASE_BOUNDS, new LaunchParams());
+ verify(firstPositioner, times(1)).onCalculate(any(), any(), any(), any(), any(), anyInt(),
+ any(), any());
final LaunchParamsModifier secondPositioner = spy(earlyExitPositioner);
mController.registerModifier(secondPositioner);
mController.calculate(null /*task*/, null /*layout*/, null /*activity*/,
- null /*source*/, null /*options*/, new LaunchParams());
- verify(firstPositioner, times(1)).onCalculate(any(), any(), any(), any(), any(), any(),
- any());
- verify(secondPositioner, times(1)).onCalculate(any(), any(), any(), any(), any(), any(),
- any());
+ null /*source*/, null /*options*/, PHASE_BOUNDS, new LaunchParams());
+ verify(firstPositioner, times(1)).onCalculate(any(), any(), any(), any(), any(), anyInt(),
+ any(), any());
+ verify(secondPositioner, times(1)).onCalculate(any(), any(), any(), any(), any(), anyInt(),
+ any(), any());
}
/**
@@ -187,9 +189,9 @@ public class LaunchParamsControllerTests extends ActivityTestsBase {
mController.registerModifier(positioner2);
mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, null /*source*/,
- null /*options*/, new LaunchParams());
+ null /*options*/, PHASE_BOUNDS, new LaunchParams());
- verify(positioner1, times(1)).onCalculate(any(), any(), any(), any(), any(),
+ verify(positioner1, times(1)).onCalculate(any(), any(), any(), any(), any(), anyInt(),
eq(positioner2.getLaunchParams()), any());
}
@@ -213,7 +215,7 @@ public class LaunchParamsControllerTests extends ActivityTestsBase {
final LaunchParams result = new LaunchParams();
mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, null /*source*/,
- null /*options*/, result);
+ null /*options*/, PHASE_BOUNDS, result);
assertEquals(result, positioner2.getLaunchParams());
}
@@ -232,21 +234,42 @@ public class LaunchParamsControllerTests extends ActivityTestsBase {
// VR activities should always land on default display.
mController.calculate(null /*task*/, null /*layout*/, vrActivity /*activity*/,
- null /*source*/, null /*options*/, result);
+ null /*source*/, null /*options*/, PHASE_BOUNDS, result);
assertEquals(DEFAULT_DISPLAY, result.mPreferredDisplayId);
// Otherwise, always lands on VR 2D display.
final ActivityRecord vr2dActivity = new ActivityBuilder(mService).build();
mController.calculate(null /*task*/, null /*layout*/, vr2dActivity /*activity*/,
- null /*source*/, null /*options*/, result);
+ null /*source*/, null /*options*/, PHASE_BOUNDS, result);
assertEquals(vr2dDisplayId, result.mPreferredDisplayId);
mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, null /*source*/,
- null /*options*/, result);
+ null /*options*/, PHASE_BOUNDS, result);
assertEquals(vr2dDisplayId, result.mPreferredDisplayId);
mService.mVr2dDisplayId = INVALID_DISPLAY;
}
+
+ /**
+ * Ensures that {@link LaunchParamsController} calculates to {@link PHASE_BOUNDS} phase by
+ * default.
+ */
+ @Test
+ public void testCalculatePhase() {
+ final LaunchParamsModifier positioner = mock(LaunchParamsModifier.class);
+ mController.registerModifier(positioner);
+
+ final ActivityRecord record = new ActivityBuilder(mService).build();
+ final ActivityRecord source = new ActivityBuilder(mService).build();
+ final WindowLayout layout = new WindowLayout(0, 0, 0, 0, 0, 0, 0);
+ final ActivityOptions options = mock(ActivityOptions.class);
+
+ mController.calculate(record.getTaskRecord(), layout, record, source, options, PHASE_BOUNDS,
+ new LaunchParams());
+ verify(positioner, times(1)).onCalculate(eq(record.getTaskRecord()), eq(layout), eq(record),
+ eq(source), eq(options), eq(PHASE_BOUNDS), any(), any());
+ }
+
/**
* Ensures that {@link LaunchParamsModifier} requests specifying display id during
* layout are honored.
@@ -348,7 +371,7 @@ public class LaunchParamsControllerTests extends ActivityTestsBase {
@Override
public int onCalculate(TaskRecord task, WindowLayout layout, ActivityRecord activity,
- ActivityRecord source, ActivityOptions options,
+ ActivityRecord source, ActivityOptions options, int phase,
LaunchParams currentParams, LaunchParams outParams) {
outParams.set(mParams);
return mReturnVal;
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index 0ff67d7b1f83..5f3a29032c22 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -24,7 +24,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -65,22 +64,26 @@ public class RecentsAnimationTest extends ActivityTestsBase {
}
@Test
- public void testCancelAnimationOnStackOrderChange() {
- ActivityStack fullscreenStack =
- mService.mRootActivityContainer.getDefaultDisplay().createStack(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- ActivityStack recentsStack = mService.mRootActivityContainer.getDefaultDisplay().createStack(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_RECENTS, true /* onTop */);
- ActivityRecord recentsActivity = new ActivityBuilder(mService)
+ public void testCancelAnimationOnVisibleStackOrderChange() {
+ ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay();
+ ActivityStack fullscreenStack = display.createStack(WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ new ActivityBuilder(mService)
+ .setComponent(new ComponentName(mContext.getPackageName(), "App1"))
+ .setCreateTask(true)
+ .setStack(fullscreenStack)
+ .build();
+ ActivityStack recentsStack = display.createStack(WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_RECENTS, true /* onTop */);
+ new ActivityBuilder(mService)
.setComponent(mRecentsComponent)
.setCreateTask(true)
.setStack(recentsStack)
.build();
- ActivityStack fullscreenStack2 =
- mService.mRootActivityContainer.getDefaultDisplay().createStack(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- ActivityRecord fsActivity = new ActivityBuilder(mService)
- .setComponent(new ComponentName(mContext.getPackageName(), "App1"))
+ ActivityStack fullscreenStack2 = display.createStack(WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ new ActivityBuilder(mService)
+ .setComponent(new ComponentName(mContext.getPackageName(), "App2"))
.setCreateTask(true)
.setStack(fullscreenStack2)
.build();
@@ -97,4 +100,42 @@ public class RecentsAnimationTest extends ActivityTestsBase {
verify(mService.mWindowManager, times(1)).cancelRecentsAnimationSynchronously(
eq(REORDER_KEEP_IN_PLACE), any());
}
+
+ @Test
+ public void testKeepAnimationOnHiddenStackOrderChange() {
+ ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay();
+ ActivityStack fullscreenStack = display.createStack(WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ new ActivityBuilder(mService)
+ .setComponent(new ComponentName(mContext.getPackageName(), "App1"))
+ .setCreateTask(true)
+ .setStack(fullscreenStack)
+ .build();
+ ActivityStack recentsStack = display.createStack(WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_RECENTS, true /* onTop */);
+ new ActivityBuilder(mService)
+ .setComponent(mRecentsComponent)
+ .setCreateTask(true)
+ .setStack(recentsStack)
+ .build();
+ ActivityStack fullscreenStack2 = display.createStack(WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ new ActivityBuilder(mService)
+ .setComponent(new ComponentName(mContext.getPackageName(), "App2"))
+ .setCreateTask(true)
+ .setStack(fullscreenStack2)
+ .build();
+ doReturn(true).when(mService.mWindowManager).canStartRecentsAnimation();
+
+ // Start the recents animation
+ Intent recentsIntent = new Intent();
+ recentsIntent.setComponent(mRecentsComponent);
+ mService.startRecentsActivity(recentsIntent, null, mock(IRecentsAnimationRunner.class));
+
+ fullscreenStack.remove();
+
+ // Ensure that the recents animation was NOT canceled
+ verify(mService.mWindowManager, times(0)).cancelRecentsAnimationSynchronously(
+ eq(REORDER_KEEP_IN_PLACE), any());
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index fe632d4ab01e..0bd681bd69a2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -173,6 +173,23 @@ public class TaskLaunchParamsModifierTests extends ActivityTestsBase {
}
@Test
+ public void testUsesTasksDisplayIdPriorToSourceIfSet() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+ final TestActivityDisplay fullscreenDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FULLSCREEN);
+
+ mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+ ActivityRecord reusableActivity = createSourceActivity(fullscreenDisplay);
+ ActivityRecord source = createSourceActivity(freeformDisplay);
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(reusableActivity.getTaskRecord(),
+ /* layout */ null, mActivity, source, /* options */ null, mCurrent, mResult));
+
+ assertEquals(fullscreenDisplay.mDisplayId, mResult.mPreferredDisplayId);
+ }
+
+ @Test
public void testUsesTaskDisplayIdIfSet() {
final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
WINDOWING_MODE_FREEFORM);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
index 29738ff2a63d..c5df85c554f4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
@@ -64,8 +64,7 @@ public class TestIWindow extends IWindow.Stub {
}
@Override
- public void windowFocusChanged(boolean hasFocus, boolean inTouchMode, boolean reportToClient)
- throws RemoteException {
+ public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) throws RemoteException {
}
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index ba81bd1c3b12..d1fe48aa63cd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -170,6 +170,10 @@ class TestWindowManagerPolicy implements WindowManagerPolicy {
}
@Override
+ public void setTopFocusedDisplay(int displayId) {
+ }
+
+ @Override
public void applyKeyguardPolicyLw(WindowState win, WindowState imeTarget) {
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
index 65cde77e7c03..20fc5ae2454e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
@@ -30,6 +30,7 @@ import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.os.Binder;
+import android.os.Build;
import android.os.IBinder;
import android.view.Display;
import android.view.IApplicationToken;
@@ -161,6 +162,7 @@ public class WindowTestUtils {
return null;
}
}, new ComponentName("", ""), false, dc, true /* fillsParent */);
+ mTargetSdk = Build.VERSION_CODES.CUR_DEVELOPMENT;
}
TestAppWindowToken(WindowManagerService service, IApplicationToken token,
diff --git a/startop/view_compiler/dex_builder.cc b/startop/view_compiler/dex_builder.cc
index 906d64c1f619..94879d0bbcc4 100644
--- a/startop/view_compiler/dex_builder.cc
+++ b/startop/view_compiler/dex_builder.cc
@@ -61,18 +61,46 @@ std::ostream& operator<<(std::ostream& out, const Instruction::Op& opcode) {
case Instruction::Op::kInvokeDirect:
out << "kInvokeDirect";
return out;
+ case Instruction::Op::kInvokeStatic:
+ out << "kInvokeStatic";
+ return out;
+ case Instruction::Op::kInvokeInterface:
+ out << "kInvokeInterface";
+ return out;
case Instruction::Op::kBindLabel:
out << "kBindLabel";
return out;
case Instruction::Op::kBranchEqz:
out << "kBranchEqz";
return out;
+ case Instruction::Op::kBranchNEqz:
+ out << "kBranchNEqz";
+ return out;
case Instruction::Op::kNew:
out << "kNew";
return out;
}
}
+std::ostream& operator<<(std::ostream& out, const Value& value) {
+ if (value.is_register()) {
+ out << "Register(" << value.value() << ")";
+ } else if (value.is_parameter()) {
+ out << "Parameter(" << value.value() << ")";
+ } else if (value.is_immediate()) {
+ out << "Immediate(" << value.value() << ")";
+ } else if (value.is_string()) {
+ out << "String(" << value.value() << ")";
+ } else if (value.is_label()) {
+ out << "Label(" << value.value() << ")";
+ } else if (value.is_type()) {
+ out << "Type(" << value.value() << ")";
+ } else {
+ out << "UnknownValue";
+ }
+ return out;
+}
+
void* TrackingAllocator::Allocate(size_t size) {
std::unique_ptr<uint8_t[]> buffer = std::make_unique<uint8_t[]>(size);
void* raw_buffer = buffer.get();
@@ -289,10 +317,16 @@ void MethodBuilder::EncodeInstruction(const Instruction& instruction) {
return EncodeInvoke(instruction, art::Instruction::INVOKE_VIRTUAL);
case Instruction::Op::kInvokeDirect:
return EncodeInvoke(instruction, art::Instruction::INVOKE_DIRECT);
+ case Instruction::Op::kInvokeStatic:
+ return EncodeInvoke(instruction, art::Instruction::INVOKE_STATIC);
+ case Instruction::Op::kInvokeInterface:
+ return EncodeInvoke(instruction, art::Instruction::INVOKE_INTERFACE);
case Instruction::Op::kBindLabel:
return BindLabel(instruction.args()[0]);
case Instruction::Op::kBranchEqz:
return EncodeBranch(art::Instruction::IF_EQZ, instruction);
+ case Instruction::Op::kBranchNEqz:
+ return EncodeBranch(art::Instruction::IF_NEZ, instruction);
case Instruction::Op::kNew:
return EncodeNew(instruction);
}
@@ -353,7 +387,9 @@ void MethodBuilder::EncodeInvoke(const Instruction& instruction, ::art::Instruct
// If there is a return value, add a move-result instruction
if (instruction.dest().has_value()) {
- Encode11x(art::Instruction::MOVE_RESULT, RegisterValue(*instruction.dest()));
+ Encode11x(instruction.result_is_object() ? art::Instruction::MOVE_RESULT_OBJECT
+ : art::Instruction::MOVE_RESULT,
+ RegisterValue(*instruction.dest()));
}
max_args_ = std::max(max_args_, instruction.args().size());
@@ -447,7 +483,7 @@ const MethodDeclData& DexBuilder::GetOrDeclareMethod(TypeDescriptor type, const
auto& ir_node = dex_file_->methods_map[new_index];
SLICER_CHECK(ir_node == nullptr);
ir_node = decl;
- decl->orig_index = new_index;
+ decl->orig_index = decl->index = new_index;
entry = {id, decl};
}
diff --git a/startop/view_compiler/dex_builder.h b/startop/view_compiler/dex_builder.h
index adf82bf9a01a..45596acfdc44 100644
--- a/startop/view_compiler/dex_builder.h
+++ b/startop/view_compiler/dex_builder.h
@@ -147,8 +147,11 @@ class Instruction {
kMove,
kInvokeVirtual,
kInvokeDirect,
+ kInvokeStatic,
+ kInvokeInterface,
kBindLabel,
kBranchEqz,
+ kBranchNEqz,
kNew
};
@@ -163,19 +166,53 @@ class Instruction {
// For most instructions, which take some number of arguments and have an optional return value.
template <typename... T>
static inline Instruction OpWithArgs(Op opcode, std::optional<const Value> dest, T... args) {
- return Instruction{opcode, /*method_id*/ 0, dest, args...};
+ return Instruction{opcode, /*method_id=*/0, /*result_is_object=*/false, dest, args...};
}
// For method calls.
template <typename... T>
static inline Instruction InvokeVirtual(size_t method_id, std::optional<const Value> dest,
Value this_arg, T... args) {
- return Instruction{Op::kInvokeVirtual, method_id, dest, this_arg, args...};
+ return Instruction{
+ Op::kInvokeVirtual, method_id, /*result_is_object=*/false, dest, this_arg, args...};
+ }
+ // Returns an object
+ template <typename... T>
+ static inline Instruction InvokeVirtualObject(size_t method_id, std::optional<const Value> dest,
+ Value this_arg, T... args) {
+ return Instruction{
+ Op::kInvokeVirtual, method_id, /*result_is_object=*/true, dest, this_arg, args...};
}
// For direct calls (basically, constructors).
template <typename... T>
static inline Instruction InvokeDirect(size_t method_id, std::optional<const Value> dest,
Value this_arg, T... args) {
- return Instruction{Op::kInvokeDirect, method_id, dest, this_arg, args...};
+ return Instruction{
+ Op::kInvokeDirect, method_id, /*result_is_object=*/false, dest, this_arg, args...};
+ }
+ // Returns an object
+ template <typename... T>
+ static inline Instruction InvokeDirectObject(size_t method_id, std::optional<const Value> dest,
+ Value this_arg, T... args) {
+ return Instruction{
+ Op::kInvokeDirect, method_id, /*result_is_object=*/true, dest, this_arg, args...};
+ }
+ // For static calls.
+ template <typename... T>
+ static inline Instruction InvokeStatic(size_t method_id, std::optional<const Value> dest,
+ T... args) {
+ return Instruction{Op::kInvokeStatic, method_id, /*result_is_object=*/false, dest, args...};
+ }
+ // Returns an object
+ template <typename... T>
+ static inline Instruction InvokeStaticObject(size_t method_id, std::optional<const Value> dest,
+ T... args) {
+ return Instruction{Op::kInvokeStatic, method_id, /*result_is_object=*/true, dest, args...};
+ }
+ // For static calls.
+ template <typename... T>
+ static inline Instruction InvokeInterface(size_t method_id, std::optional<const Value> dest,
+ T... args) {
+ return Instruction{Op::kInvokeInterface, method_id, /*result_is_object=*/false, dest, args...};
}
///////////////
@@ -184,21 +221,27 @@ class Instruction {
Op opcode() const { return opcode_; }
size_t method_id() const { return method_id_; }
+ bool result_is_object() const { return result_is_object_; }
const std::optional<const Value>& dest() const { return dest_; }
const std::vector<const Value>& args() const { return args_; }
private:
inline Instruction(Op opcode, size_t method_id, std::optional<const Value> dest)
- : opcode_{opcode}, method_id_{method_id}, dest_{dest}, args_{} {}
+ : opcode_{opcode}, method_id_{method_id}, result_is_object_{false}, dest_{dest}, args_{} {}
template <typename... T>
- inline constexpr Instruction(Op opcode, size_t method_id, std::optional<const Value> dest,
- T... args)
- : opcode_{opcode}, method_id_{method_id}, dest_{dest}, args_{args...} {}
+ inline constexpr Instruction(Op opcode, size_t method_id, bool result_is_object,
+ std::optional<const Value> dest, T... args)
+ : opcode_{opcode},
+ method_id_{method_id},
+ result_is_object_{result_is_object},
+ dest_{dest},
+ args_{args...} {}
const Op opcode_;
// The index of the method to invoke, for kInvokeVirtual and similar opcodes.
const size_t method_id_{0};
+ const bool result_is_object_;
const std::optional<const Value> dest_;
const std::vector<const Value> args_;
};
@@ -244,6 +287,8 @@ class MethodBuilder {
// TODO: add builders for more instructions
+ DexBuilder* dex_file() const { return dex_; }
+
private:
void EncodeInstructions();
void EncodeInstruction(const Instruction& instruction);
diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java
index e20f3a9406c0..1508ad9ee56b 100644
--- a/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java
+++ b/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java
@@ -84,6 +84,15 @@ public class DexBuilderTest {
}
@Test
+ public void returnIfNotZero() throws Exception {
+ ClassLoader loader = loadDexFile("simple.dex");
+ Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
+ Method method = clazz.getMethod("returnIfNotZero", int.class);
+ Assert.assertEquals(3, method.invoke(null, 0));
+ Assert.assertEquals(5, method.invoke(null, 17));
+ }
+
+ @Test
public void backwardsBranch() throws Exception {
ClassLoader loader = loadDexFile("simple.dex");
Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
@@ -124,4 +133,22 @@ public class DexBuilderTest {
Assert.assertEquals("b", method.invoke(null, 0));
Assert.assertEquals("a", method.invoke(null, 1));
}
+
+ @Test
+ public void invokeStaticReturnObject() throws Exception {
+ ClassLoader loader = loadDexFile("simple.dex");
+ Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
+ Method method = clazz.getMethod("invokeStaticReturnObject", int.class, int.class);
+ Assert.assertEquals("10", method.invoke(null, 10, 10));
+ Assert.assertEquals("a", method.invoke(null, 10, 16));
+ Assert.assertEquals("5", method.invoke(null, 5, 16));
+ }
+
+ @Test
+ public void invokeVirtualReturnObject() throws Exception {
+ ClassLoader loader = loadDexFile("simple.dex");
+ Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
+ Method method = clazz.getMethod("invokeVirtualReturnObject", String.class, int.class);
+ Assert.assertEquals("bc", method.invoke(null, "abc", 1));
+ }
}
diff --git a/startop/view_compiler/dex_testcase_generator.cc b/startop/view_compiler/dex_testcase_generator.cc
index e2bf43bc1d0c..2781aa55d1df 100644
--- a/startop/view_compiler/dex_testcase_generator.cc
+++ b/startop/view_compiler/dex_testcase_generator.cc
@@ -108,6 +108,27 @@ void GenerateSimpleTestCases(const string& outdir) {
}
returnIfZero.Encode();
+ // int returnIfNotZero(int x) { if (x != 0) { return 5; } else { return 3; } }
+ MethodBuilder returnIfNotZero{cbuilder.CreateMethod(
+ "returnIfNotZero", Prototype{TypeDescriptor::Int(), TypeDescriptor::Int()})};
+ {
+ Value resultIfNotZero{returnIfNotZero.MakeRegister()};
+ Value else_target{returnIfNotZero.MakeLabel()};
+ returnIfNotZero.AddInstruction(Instruction::OpWithArgs(
+ Instruction::Op::kBranchNEqz, /*dest=*/{}, Value::Parameter(0), else_target));
+ // else branch
+ returnIfNotZero.BuildConst4(resultIfNotZero, 3);
+ returnIfNotZero.AddInstruction(
+ Instruction::OpWithArgs(Instruction::Op::kReturn, /*dest=*/{}, resultIfNotZero));
+ // then branch
+ returnIfNotZero.AddInstruction(
+ Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, else_target));
+ returnIfNotZero.BuildConst4(resultIfNotZero, 5);
+ returnIfNotZero.AddInstruction(
+ Instruction::OpWithArgs(Instruction::Op::kReturn, /*dest=*/{}, resultIfNotZero));
+ }
+ returnIfNotZero.Encode();
+
// Make sure backwards branches work too.
//
// Pseudo code for test:
@@ -216,6 +237,38 @@ void GenerateSimpleTestCases(const string& outdir) {
method.Encode();
}(returnStringIfZeroBA);
+ // Make sure we can invoke static methods that return an object
+ // String invokeStaticReturnObject(int n, int radix) { return java.lang.Integer.toString(n,
+ // radix); }
+ MethodBuilder invokeStaticReturnObject{
+ cbuilder.CreateMethod("invokeStaticReturnObject",
+ Prototype{string_type, TypeDescriptor::Int(), TypeDescriptor::Int()})};
+ [&](MethodBuilder& method) {
+ Value result{method.MakeRegister()};
+ MethodDeclData to_string{dex_file.GetOrDeclareMethod(
+ TypeDescriptor::FromClassname("java.lang.Integer"),
+ "toString",
+ Prototype{string_type, TypeDescriptor::Int(), TypeDescriptor::Int()})};
+ method.AddInstruction(Instruction::InvokeStaticObject(
+ to_string.id, result, Value::Parameter(0), Value::Parameter(1)));
+ method.BuildReturn(result, /*is_object=*/true);
+ method.Encode();
+ }(invokeStaticReturnObject);
+
+ // Make sure we can invoke virtual methods that return an object
+ // String invokeVirtualReturnObject(String s, int n) { return s.substring(n); }
+ MethodBuilder invokeVirtualReturnObject{cbuilder.CreateMethod(
+ "invokeVirtualReturnObject", Prototype{string_type, string_type, TypeDescriptor::Int()})};
+ [&](MethodBuilder& method) {
+ Value result{method.MakeRegister()};
+ MethodDeclData substring{dex_file.GetOrDeclareMethod(
+ string_type, "substring", Prototype{string_type, TypeDescriptor::Int()})};
+ method.AddInstruction(Instruction::InvokeVirtualObject(
+ substring.id, result, Value::Parameter(0), Value::Parameter(1)));
+ method.BuildReturn(result, /*is_object=*/true);
+ method.Encode();
+ }(invokeVirtualReturnObject);
+
slicer::MemView image{dex_file.CreateImage()};
std::ofstream out_file(outdir + "/simple.dex");
out_file.write(image.ptr<const char>(), image.size());
diff --git a/telecomm/java/android/telecom/DefaultDialerManager.java b/telecomm/java/android/telecom/DefaultDialerManager.java
index 1806aee27e31..2680af76d803 100644
--- a/telecomm/java/android/telecom/DefaultDialerManager.java
+++ b/telecomm/java/android/telecom/DefaultDialerManager.java
@@ -74,7 +74,7 @@ public class DefaultDialerManager {
}
// Only make the change if the new package belongs to a valid phone application
- List<String> packageNames = getInstalledDialerApplications(context);
+ List<String> packageNames = getInstalledDialerApplications(context, user);
if (packageNames.contains(packageName)) {
// Update the secure setting.
diff --git a/telecomm/java/android/telecom/VideoProfile.java b/telecomm/java/android/telecom/VideoProfile.java
index bbac8eb88aec..7b2306128b7b 100644
--- a/telecomm/java/android/telecom/VideoProfile.java
+++ b/telecomm/java/android/telecom/VideoProfile.java
@@ -369,16 +369,13 @@ public class VideoProfile implements Parcelable {
}
/**
- * Create a call camera capabilities instance that optionally
- * supports zoom.
+ * Create a call camera capabilities instance that optionally supports zoom.
*
* @param width The width of the camera video (in pixels).
* @param height The height of the camera video (in pixels).
* @param zoomSupported True when camera supports zoom.
* @param maxZoom Maximum zoom supported by camera.
- * @hide
*/
- @UnsupportedAppUsage
public CameraCapabilities(int width, int height, boolean zoomSupported, float maxZoom) {
mWidth = width;
mHeight = height;
@@ -455,16 +452,14 @@ public class VideoProfile implements Parcelable {
}
/**
- * Whether the camera supports zoom.
- * @hide
+ * Returns {@code true} is zoom is supported, {@code false} otherwise.
*/
public boolean isZoomSupported() {
return mZoomSupported;
}
/**
- * The maximum zoom supported by the camera.
- * @hide
+ * Returns the maximum zoom supported by the camera.
*/
public float getMaxZoom() {
return mMaxZoom;
diff --git a/telephony/java/android/telephony/NetworkService.java b/telephony/java/android/telephony/NetworkService.java
index 4354314dac29..4bca404d9444 100644
--- a/telephony/java/android/telephony/NetworkService.java
+++ b/telephony/java/android/telephony/NetworkService.java
@@ -16,7 +16,6 @@
package android.telephony;
-import android.annotation.CallSuper;
import android.annotation.SystemApi;
import android.app.Service;
import android.content.Intent;
@@ -53,7 +52,6 @@ public abstract class NetworkService extends Service {
private final String TAG = NetworkService.class.getSimpleName();
public static final String NETWORK_SERVICE_INTERFACE = "android.telephony.NetworkService";
- public static final String NETWORK_SERVICE_EXTRA_SLOT_ID = "android.telephony.extra.SLOT_ID";
private static final int NETWORK_SERVICE_CREATE_NETWORK_SERVICE_PROVIDER = 1;
private static final int NETWORK_SERVICE_REMOVE_NETWORK_SERVICE_PROVIDER = 2;
@@ -81,7 +79,7 @@ public abstract class NetworkService extends Service {
* must extend this class to support network connection. Note that each instance of network
* service is associated with one physical SIM slot.
*/
- public class NetworkServiceProvider {
+ public abstract class NetworkServiceProvider implements AutoCloseable {
private final int mSlotId;
private final List<INetworkServiceCallback>
@@ -137,12 +135,12 @@ public abstract class NetworkService extends Service {
}
/**
- * Called when the instance of network service is destroyed (e.g. got unbind or binder died).
+ * Called when the instance of network service is destroyed (e.g. got unbind or binder died)
+ * or when the network service provider is removed. The extended class should implement this
+ * method to perform cleanup works.
*/
- @CallSuper
- protected void onDestroy() {
- mNetworkRegistrationStateChangedCallbacks.clear();
- }
+ @Override
+ public abstract void close();
}
private class NetworkServiceHandler extends Handler {
@@ -168,7 +166,7 @@ public abstract class NetworkService extends Service {
case NETWORK_SERVICE_REMOVE_NETWORK_SERVICE_PROVIDER:
// If the service provider doesn't exist yet, we try to create it.
if (serviceProvider != null) {
- serviceProvider.onDestroy();
+ serviceProvider.close();
mServiceMap.remove(slotId);
}
break;
@@ -176,7 +174,7 @@ public abstract class NetworkService extends Service {
for (int i = 0; i < mServiceMap.size(); i++) {
serviceProvider = mServiceMap.get(i);
if (serviceProvider != null) {
- serviceProvider.onDestroy();
+ serviceProvider.close();
}
}
mServiceMap.clear();
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index dacc5d86e9ae..a7e8e8a95ea2 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -80,6 +80,12 @@ public class SubscriptionInfo implements Parcelable {
private CharSequence mCarrierName;
/**
+ * The subscription carrier id.
+ * @see TelephonyManager#getSimCarrierId()
+ */
+ private int mCarrierId;
+
+ /**
* The source of the name, NAME_SOURCE_UNDEFINED, NAME_SOURCE_DEFAULT_SOURCE,
* NAME_SOURCE_SIM_SOURCE or NAME_SOURCE_USER_INPUT.
*/
@@ -171,7 +177,7 @@ public class SubscriptionInfo implements Parcelable {
@Nullable UiccAccessRule[] accessRules, String cardId) {
this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number,
roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardId,
- false, null, true);
+ false, null, true, TelephonyManager.UNKNOWN_CARRIER_ID);
}
/**
@@ -181,10 +187,10 @@ public class SubscriptionInfo implements Parcelable {
CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
@Nullable UiccAccessRule[] accessRules, String cardId, boolean isOpportunistic,
- @Nullable String groupUUID, boolean isMetered) {
+ @Nullable String groupUUID, boolean isMetered, int carrierId) {
this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number,
roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardId,
- isOpportunistic, groupUUID, isMetered, false);
+ isOpportunistic, groupUUID, isMetered, false, carrierId);
}
/**
* @hide
@@ -193,7 +199,7 @@ public class SubscriptionInfo implements Parcelable {
CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
@Nullable UiccAccessRule[] accessRules, String cardId, boolean isOpportunistic,
- @Nullable String groupUUID, boolean isMetered, boolean isGroupDisabled) {
+ @Nullable String groupUUID, boolean isMetered, boolean isGroupDisabled, int carrierid) {
this.mId = id;
this.mIccId = iccId;
this.mSimSlotIndex = simSlotIndex;
@@ -214,6 +220,7 @@ public class SubscriptionInfo implements Parcelable {
this.mGroupUUID = groupUUID;
this.mIsMetered = isMetered;
this.mIsGroupDisabled = isGroupDisabled;
+ this.mCarrierId = carrierid;
}
@@ -239,6 +246,14 @@ public class SubscriptionInfo implements Parcelable {
}
/**
+ * @return the carrier id of this Subscription carrier.
+ * @see TelephonyManager#getSimCarrierId()
+ */
+ public int getCarrierId() {
+ return this.mCarrierId;
+ }
+
+ /**
* @return the name displayed to the user that identifies this subscription
*/
public CharSequence getDisplayName() {
@@ -554,11 +569,12 @@ public class SubscriptionInfo implements Parcelable {
String groupUUID = source.readString();
boolean isMetered = source.readBoolean();
boolean isGroupDisabled = source.readBoolean();
+ int carrierid = source.readInt();
return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName,
nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso,
isEmbedded, accessRules, cardId, isOpportunistic, groupUUID, isMetered,
- isGroupDisabled);
+ isGroupDisabled, carrierid);
}
@Override
@@ -589,6 +605,7 @@ public class SubscriptionInfo implements Parcelable {
dest.writeString(mGroupUUID);
dest.writeBoolean(mIsMetered);
dest.writeBoolean(mIsGroupDisabled);
+ dest.writeInt(mCarrierId);
}
@Override
@@ -616,8 +633,9 @@ public class SubscriptionInfo implements Parcelable {
String iccIdToPrint = givePrintableIccid(mIccId);
String cardIdToPrint = givePrintableIccid(mCardId);
return "{id=" + mId + ", iccId=" + iccIdToPrint + " simSlotIndex=" + mSimSlotIndex
- + " displayName=" + mDisplayName + " carrierName=" + mCarrierName
- + " nameSource=" + mNameSource + " iconTint=" + mIconTint + " mNumber=" + mNumber
+ + " carrierId=" + mCarrierId + " displayName=" + mDisplayName
+ + " carrierName=" + mCarrierName + " nameSource=" + mNameSource
+ + " iconTint=" + mIconTint + " mNumber=" + mNumber
+ " dataRoaming=" + mDataRoaming + " iconBitmap=" + mIconBitmap + " mcc " + mMcc
+ " mnc " + mMnc + "mCountryIso=" + mCountryIso + " isEmbedded " + mIsEmbedded
+ " accessRules " + Arrays.toString(mAccessRules)
@@ -630,7 +648,8 @@ public class SubscriptionInfo implements Parcelable {
public int hashCode() {
return Objects.hash(mId, mSimSlotIndex, mNameSource, mIconTint, mDataRoaming, mIsEmbedded,
mIsOpportunistic, mGroupUUID, mIsMetered, mIccId, mNumber, mMcc, mMnc,
- mCountryIso, mCardId, mDisplayName, mCarrierName, mAccessRules, mIsGroupDisabled);
+ mCountryIso, mCardId, mDisplayName, mCarrierName, mAccessRules, mIsGroupDisabled,
+ mCarrierId);
}
@Override
@@ -653,8 +672,9 @@ public class SubscriptionInfo implements Parcelable {
&& mIsEmbedded == toCompare.mIsEmbedded
&& mIsOpportunistic == toCompare.mIsOpportunistic
&& mIsGroupDisabled == toCompare.mIsGroupDisabled
- && Objects.equals(mGroupUUID, toCompare.mGroupUUID)
+ && mCarrierId == toCompare.mCarrierId
&& mIsMetered == toCompare.mIsMetered
+ && Objects.equals(mGroupUUID, toCompare.mGroupUUID)
&& Objects.equals(mIccId, toCompare.mIccId)
&& Objects.equals(mNumber, toCompare.mNumber)
&& Objects.equals(mMcc, toCompare.mMcc)
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 387453fa3985..eaff50a522ea 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -382,6 +382,14 @@ public class SubscriptionManager {
public static final int SIM_PROVISIONED = 0;
/**
+ * TelephonyProvider column name for subscription carrier id.
+ * @see TelephonyManager#getSimCarrierId()
+ * <p>Type: INTEGER (int) </p>
+ * @hide
+ */
+ public static final String CARRIER_ID = "carrier_id";
+
+ /**
* TelephonyProvider column name for the MCC associated with a SIM, stored as a string.
* <P>Type: TEXT (String)</P>
* @hide
@@ -572,7 +580,6 @@ public class SubscriptionManager {
* TelephonyProvider column name for whether a subscription is opportunistic, that is,
* whether the network it connects to is limited in functionality or coverage.
* For example, CBRS.
- * IS_EMBEDDED should always be true.
* <p>Type: INTEGER (int), 1 for opportunistic or 0 for non-opportunistic.
* @hide
*/
@@ -2379,18 +2386,32 @@ public class SubscriptionManager {
}
/**
- * Set opportunistic by simInfo index
+ * Set whether a subscription is opportunistic, that is, whether the network it connects
+ * to has limited coverage. For example, CBRS. Setting a subscription opportunistic has
+ * following impacts:
+ * 1) Even if it's active, it will be dormant most of the time. The modem will not try
+ * to scan or camp until it knows an available network is nearby to save power.
+ * 2) Telephony relies on system app or carrier input to notify nearby available networks.
+ * See {@link TelephonyManager#updateAvailableNetworks(List)} for more information.
+ * 3) In multi-SIM devices, when the network is nearby and camped, system may automatically
+ * switch internet data between it and default data subscription, based on carrier
+ * recommendation and its signal strength and metered-ness, etc.
+ *
+ *
+ * Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE} or carrier
+ * privilege permission of the subscription.
*
* @param opportunistic whether it’s opportunistic subscription.
* @param subId the unique SubscriptionInfo index in database
- * @return the number of records updated
- * @hide
+ * @return {@code true} if the operation is succeed, {@code false} otherwise.
*/
+ @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public int setOpportunistic(boolean opportunistic, int subId) {
+ public boolean setOpportunistic(boolean opportunistic, int subId) {
if (VDBG) logd("[setOpportunistic]+ opportunistic:" + opportunistic + " subId:" + subId);
return setSubscriptionPropertyHelper(subId, "setOpportunistic",
- (iSub)-> iSub.setOpportunistic(opportunistic, subId));
+ (iSub)-> iSub.setOpportunistic(
+ opportunistic, subId, mContext.getOpPackageName())) == 1;
}
/**
@@ -2510,18 +2531,26 @@ public class SubscriptionManager {
}
/**
- * Set metered by simInfo index
+ * Set if a subscription is metered or not. Similar to Wi-Fi, metered means
+ * user may be charged more if more data is used.
+ *
+ * By default all Cellular networks are considered metered. System or carrier privileged apps
+ * can set a subscription un-metered which will be considered when system switches data between
+ * primary subscription and opportunistic subscription.
+ *
+ * Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE} or carrier
+ * privilege permission of the subscription.
*
* @param isMetered whether it’s a metered subscription.
* @param subId the unique SubscriptionInfo index in database
- * @return the number of records updated
- * @hide
+ * @return {@code true} if the operation is succeed, {@code false} otherwise.
*/
+ @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public int setMetered(boolean isMetered, int subId) {
+ public boolean setMetered(boolean isMetered, int subId) {
if (VDBG) logd("[setIsMetered]+ isMetered:" + isMetered + " subId:" + subId);
return setSubscriptionPropertyHelper(subId, "setIsMetered",
- (iSub)-> iSub.setMetered(isMetered, subId));
+ (iSub)-> iSub.setMetered(isMetered, subId, mContext.getOpPackageName())) == 1;
}
/**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index e2d03a048af5..e0632b1e0392 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -6671,8 +6671,8 @@ public class TelephonyManager {
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- return telephony.getCarrierPrivilegeStatus(subId) ==
- CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
+ return telephony.getCarrierPrivilegeStatus(subId)
+ == CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
}
} catch (RemoteException ex) {
Rlog.e(TAG, "hasCarrierPrivileges RemoteException", ex);
diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java
index 1db58506bb1c..74d1e838f186 100644
--- a/telephony/java/android/telephony/data/DataService.java
+++ b/telephony/java/android/telephony/data/DataService.java
@@ -16,7 +16,6 @@
package android.telephony.data;
-import android.annotation.CallSuper;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -60,7 +59,6 @@ public abstract class DataService extends Service {
private static final String TAG = DataService.class.getSimpleName();
public static final String DATA_SERVICE_INTERFACE = "android.telephony.data.DataService";
- public static final String DATA_SERVICE_EXTRA_SLOT_ID = "android.telephony.data.extra.SLOT_ID";
/** {@hide} */
@IntDef(prefix = "REQUEST_REASON_", value = {
@@ -116,7 +114,7 @@ public abstract class DataService extends Service {
* must extend this class to support data connection. Note that each instance of data service
* provider is associated with one physical SIM slot.
*/
- public class DataServiceProvider {
+ public abstract class DataServiceProvider implements AutoCloseable {
private final int mSlotId;
@@ -250,12 +248,12 @@ public abstract class DataService extends Service {
}
/**
- * Called when the instance of data service is destroyed (e.g. got unbind or binder died).
+ * Called when the instance of data service is destroyed (e.g. got unbind or binder died)
+ * or when the data service provider is removed. The extended class should implement this
+ * method to perform cleanup works.
*/
- @CallSuper
- protected void onDestroy() {
- mDataCallListChangedCallbacks.clear();
- }
+ @Override
+ public abstract void close();
}
private static final class SetupDataCallRequest {
@@ -345,7 +343,7 @@ public abstract class DataService extends Service {
break;
case DATA_SERVICE_REMOVE_DATA_SERVICE_PROVIDER:
if (serviceProvider != null) {
- serviceProvider.onDestroy();
+ serviceProvider.close();
mServiceMap.remove(slotId);
}
break;
@@ -353,7 +351,7 @@ public abstract class DataService extends Service {
for (int i = 0; i < mServiceMap.size(); i++) {
serviceProvider = mServiceMap.get(i);
if (serviceProvider != null) {
- serviceProvider.onDestroy();
+ serviceProvider.close();
}
}
mServiceMap.clear();
diff --git a/telephony/java/android/telephony/data/QualifiedNetworksService.java b/telephony/java/android/telephony/data/QualifiedNetworksService.java
index 57d9cced53b1..45b4849990c2 100644
--- a/telephony/java/android/telephony/data/QualifiedNetworksService.java
+++ b/telephony/java/android/telephony/data/QualifiedNetworksService.java
@@ -151,7 +151,7 @@ public abstract class QualifiedNetworksService extends Service {
/**
* Called when the qualified networks updater is removed. The extended class should
- * implement this method to perform clean up works.
+ * implement this method to perform cleanup works.
*/
@Override
public abstract void close();
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index b0c875e0c6f2..0fdca5d24a20 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -79,10 +79,7 @@ public class DctConstants {
public static final int EVENT_PS_RESTRICT_DISABLED = BASE + 23;
public static final int EVENT_CLEAN_UP_CONNECTION = BASE + 24;
public static final int EVENT_RESTART_RADIO = BASE + 26;
- public static final int EVENT_SET_INTERNAL_DATA_ENABLE = BASE + 27;
public static final int EVENT_CLEAN_UP_ALL_CONNECTIONS = BASE + 29;
- public static final int CMD_SET_USER_DATA_ENABLE = BASE + 30;
- public static final int CMD_SET_POLICY_DATA_ENABLE = BASE + 32;
public static final int EVENT_ICC_CHANGED = BASE + 33;
public static final int EVENT_DISCONNECT_DC_RETRYING = BASE + 34;
public static final int EVENT_DATA_SETUP_COMPLETE_ERROR = BASE + 35;
@@ -93,14 +90,12 @@ public class DctConstants {
public static final int CMD_NET_STAT_POLL = BASE + 40;
public static final int EVENT_DATA_RAT_CHANGED = BASE + 41;
public static final int CMD_CLEAR_PROVISIONING_SPINNER = BASE + 42;
- public static final int EVENT_DEVICE_PROVISIONED_CHANGE = BASE + 43;
public static final int EVENT_REDIRECTION_DETECTED = BASE + 44;
public static final int EVENT_PCO_DATA_RECEIVED = BASE + 45;
- public static final int EVENT_SET_CARRIER_DATA_ENABLED = BASE + 46;
+ public static final int EVENT_DATA_ENABLED_CHANGED = BASE + 46;
public static final int EVENT_DATA_RECONNECT = BASE + 47;
public static final int EVENT_ROAMING_SETTING_CHANGE = BASE + 48;
public static final int EVENT_DATA_SERVICE_BINDING_CHANGED = BASE + 49;
- public static final int EVENT_DEVICE_PROVISIONING_DATA_SETTING_CHANGE = BASE + 50;
/***** Constants *****/
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 65d1a920a324..65eedb83c4cc 100755
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -162,7 +162,7 @@ interface ISub {
* @param subId the unique SubscriptionInfo index in database
* @return the number of records updated
*/
- int setOpportunistic(boolean opportunistic, int subId);
+ int setOpportunistic(boolean opportunistic, int subId, String callingPackage);
/**
* Inform SubscriptionManager that subscriptions in the list are bundled
@@ -190,7 +190,7 @@ interface ISub {
* @param subId the unique SubscriptionInfo index in database
* @return the number of records updated
*/
- int setMetered(boolean isMetered, int subId);
+ int setMetered(boolean isMetered, int subId, String callingPackage);
/**
* Set which subscription is preferred for cellular data. It's
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 21d6b94fba24..a8e82b3d1bc6 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -66,6 +66,8 @@ interface IWifiManager
List<OsuProvider> getMatchingOsuProviders(in List<ScanResult> scanResult);
+ Map getMatchingPasspointConfigsForOsuProviders(in List<OsuProvider> osuProviders);
+
int addOrUpdateNetwork(in WifiConfiguration config, String packageName);
boolean addOrUpdatePasspointConfiguration(in PasspointConfiguration config, String packageName);
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 57c97eaf1f10..8cb20638dd42 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -57,8 +57,11 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.net.InetAddress;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.concurrent.CountDownLatch;
/**
@@ -1230,6 +1233,30 @@ public class WifiManager {
}
/**
+ * Returns the matching Passpoint R2 configurations for given OSU (Online Sign-Up) providers.
+ *
+ * Given a list of OSU providers, this only returns OSU providers that already have Passpoint R2
+ * configurations in the device.
+ * An empty map will be returned when there is no matching Passpoint R2 configuration for the
+ * given OsuProviders.
+ *
+ * @param osuProviders a set of {@link OsuProvider}
+ * @return Map that consists of {@link OsuProvider} and matching {@link PasspointConfiguration}.
+ * @throws UnsupportedOperationException if Passpoint is not enabled on the device.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ public Map<OsuProvider, PasspointConfiguration> getMatchingPasspointConfigsForOsuProviders(
+ @NonNull Set<OsuProvider> osuProviders) {
+ try {
+ return mService.getMatchingPasspointConfigsForOsuProviders(
+ new ArrayList<>(osuProviders));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Add a new network description to the set of configured networks.
* The {@code networkId} field of the supplied configuration object
* is ignored.
diff --git a/wifi/java/android/net/wifi/aware/DiscoverySession.java b/wifi/java/android/net/wifi/aware/DiscoverySession.java
index 699f54cf13fb..a47e70b6f30f 100644
--- a/wifi/java/android/net/wifi/aware/DiscoverySession.java
+++ b/wifi/java/android/net/wifi/aware/DiscoverySession.java
@@ -34,11 +34,11 @@ import java.lang.ref.WeakReference;
* {@link PublishDiscoverySession} and {@link SubscribeDiscoverySession}. This
* class provides functionality common to both publish and subscribe discovery sessions:
* <ul>
- * <li>Sending messages: {@link #sendMessage(PeerHandle, int, byte[])} method.
- * <li>Creating a network-specifier when requesting a Aware connection:
- * {@link #createNetworkSpecifierOpen(PeerHandle)} or
- * {@link #createNetworkSpecifierPassphrase(PeerHandle, String)}.
+ * <li>Sending messages: {@link #sendMessage(PeerHandle, int, byte[])} method.
+ * <li>Creating a network-specifier when requesting a Aware connection using
+ * {@link WifiAwareManager.NetworkSpecifierBuilder}.
* </ul>
+ * <p>
* The {@link #close()} method must be called to destroy discovery sessions once they are
* no longer needed.
*/
@@ -270,6 +270,7 @@ public class DiscoverySession implements AutoCloseable {
* <p>
* To set up an encrypted link use the
* {@link #createNetworkSpecifierPassphrase(PeerHandle, String)} API.
+ * @deprecated Use the replacement {@link WifiAwareManager.NetworkSpecifierBuilder}.
*
* @param peerHandle The peer's handle obtained through
* {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], java.util.List)}
@@ -284,6 +285,7 @@ public class DiscoverySession implements AutoCloseable {
* android.net.ConnectivityManager.NetworkCallback)}
* [or other varieties of that API].
*/
+ @Deprecated
public NetworkSpecifier createNetworkSpecifierOpen(@NonNull PeerHandle peerHandle) {
if (mTerminated) {
Log.w(TAG, "createNetworkSpecifierOpen: called on terminated session");
@@ -318,6 +320,7 @@ public class DiscoverySession implements AutoCloseable {
* <p>
* Note: per the Wi-Fi Aware specification the roles are fixed - a Subscriber is an INITIATOR
* and a Publisher is a RESPONDER.
+ * @deprecated Use the replacement {@link WifiAwareManager.NetworkSpecifierBuilder}.
*
* @param peerHandle The peer's handle obtained through
* {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle,
@@ -336,6 +339,7 @@ public class DiscoverySession implements AutoCloseable {
* android.net.ConnectivityManager.NetworkCallback)}
* [or other varieties of that API].
*/
+ @Deprecated
public NetworkSpecifier createNetworkSpecifierPassphrase(
@NonNull PeerHandle peerHandle, @NonNull String passphrase) {
if (!WifiAwareUtils.validatePassphrase(passphrase)) {
@@ -376,6 +380,7 @@ public class DiscoverySession implements AutoCloseable {
* <p>
* Note: per the Wi-Fi Aware specification the roles are fixed - a Subscriber is an INITIATOR
* and a Publisher is a RESPONDER.
+ * @deprecated Use the replacement {@link WifiAwareManager.NetworkSpecifierBuilder}.
*
* @param peerHandle The peer's handle obtained through
* {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle,
@@ -397,6 +402,7 @@ public class DiscoverySession implements AutoCloseable {
*
* @hide
*/
+ @Deprecated
@SystemApi
public NetworkSpecifier createNetworkSpecifierPmk(@NonNull PeerHandle peerHandle,
@NonNull byte[] pmk) {
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
index 8529a89a9914..26a6c08bee29 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
@@ -21,6 +21,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.Context;
import android.net.ConnectivityManager;
@@ -57,11 +58,7 @@ import java.util.List;
* {@link WifiAwareSession#subscribe(SubscribeConfig, DiscoverySessionCallback, Handler)}.
* <li>Create a Aware network specifier to be used with
* {@link ConnectivityManager#requestNetwork(NetworkRequest, ConnectivityManager.NetworkCallback)}
- * to set-up a Aware connection with a peer. Refer to
- * {@link DiscoverySession#createNetworkSpecifierOpen(PeerHandle)},
- * {@link DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String)},
- * {@link WifiAwareSession#createNetworkSpecifierOpen(int, byte[])}, and
- * {@link WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String)}.
+ * to set-up a Aware connection with a peer. Refer to {@link NetworkSpecifierBuilder}.
* </ul>
* <p>
* Aware may not be usable when Wi-Fi is disabled (and other conditions). To validate that
@@ -110,10 +107,7 @@ import java.util.List;
* <li>{@link NetworkRequest.Builder#addTransportType(int)} of
* {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
* <li>{@link NetworkRequest.Builder#setNetworkSpecifier(String)} using
- * {@link WifiAwareSession#createNetworkSpecifierOpen(int, byte[])},
- * {@link WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String)},
- * {@link DiscoverySession#createNetworkSpecifierOpen(PeerHandle)}, or
- * {@link DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String)}.
+ * {@link NetworkSpecifierBuilder}.
* </ul>
*/
@SystemService(Context.WIFI_AWARE_SERVICE)
@@ -145,8 +139,6 @@ public class WifiAwareManager {
* Connection creation role is that of INITIATOR. Used to create a network specifier string
* when requesting a Aware network.
*
- * @see DiscoverySession#createNetworkSpecifierOpen(PeerHandle)
- * @see DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String)
* @see WifiAwareSession#createNetworkSpecifierOpen(int, byte[])
* @see WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String)
*/
@@ -156,8 +148,6 @@ public class WifiAwareManager {
* Connection creation role is that of RESPONDER. Used to create a network specifier string
* when requesting a Aware network.
*
- * @see DiscoverySession#createNetworkSpecifierOpen(PeerHandle)
- * @see DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String)
* @see WifiAwareSession#createNetworkSpecifierOpen(int, byte[])
* @see WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String)
*/
@@ -415,6 +405,11 @@ public class WifiAwareManager {
+ ", passphrase=" + ((passphrase == null) ? "null" : "non-null"));
}
+ if (!WifiAwareUtils.isLegacyVersion(mContext, Build.VERSION_CODES.Q)) {
+ throw new UnsupportedOperationException(
+ "API not deprecated - use WifiAwareManager.NetworkSpecifierBuilder");
+ }
+
if (role != WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
&& role != WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) {
throw new IllegalArgumentException(
@@ -813,4 +808,135 @@ public class WifiAwareManager {
mOriginalCallback.onSessionTerminated();
}
}
+
+ /**
+ * A builder class for a Wi-Fi Aware network specifier to set up an Aware connection with a
+ * peer.
+ * <p>
+ * Note that all Wi-Fi Aware connection specifier objects must call the
+ * {@link NetworkSpecifierBuilder#setDiscoverySession(DiscoverySession)} to specify the context
+ * within which the connection is created, and
+ * {@link NetworkSpecifierBuilder#setPeerHandle(PeerHandle)} to specify the peer to which the
+ * connection is created.
+ */
+ public static class NetworkSpecifierBuilder {
+ private DiscoverySession mDiscoverySession;
+ private PeerHandle mPeerHandle;
+ private String mPskPassphrase;
+ private byte[] mPmk;
+
+ /**
+ * Configure the {@link PublishDiscoverySession} or {@link SubscribeDiscoverySession}
+ * discovery session in whose context the connection is created.
+ * <p>
+ * Note: this method must be called for any connection request!
+ *
+ * @param discoverySession A Wi-Fi Aware discovery session.
+ * @return the current {@link NetworkSpecifierBuilder} builder, enabling chaining of builder
+ * methods.
+ */
+ public @NonNull NetworkSpecifierBuilder setDiscoverySession(
+ @NonNull DiscoverySession discoverySession) {
+ if (discoverySession == null) {
+ throw new IllegalArgumentException("Non-null discoverySession required");
+ }
+ mDiscoverySession = discoverySession;
+ return this;
+ }
+
+ /**
+ * Configure the {@link PeerHandle} of the peer to which the Wi-Fi Aware connection is
+ * requested. The peer is discovered through Wi-Fi Aware discovery,
+ * <p>
+ * Note: this method must be called for any connection request!
+ *
+ * @param peerHandle The peer's handle obtained through
+ * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], java.util.List)}
+ * or
+ * {@link DiscoverySessionCallback#onMessageReceived(PeerHandle, byte[])}.
+ * @return the current {@link NetworkSpecifierBuilder} builder, enabling chaining of builder
+ * methods.
+ */
+ public @NonNull NetworkSpecifierBuilder setPeerHandle(@NonNull PeerHandle peerHandle) {
+ if (peerHandle == null) {
+ throw new IllegalArgumentException("Non-null peerHandle required");
+ }
+ mPeerHandle = peerHandle;
+ return this;
+ }
+
+ /**
+ * Configure the PSK Passphrase for the Wi-Fi Aware connection being requested. This method
+ * is optional - if not called, then an Open (unencrypted) connection will be created.
+ *
+ * @param pskPassphrase The (optional) passphrase to be used to encrypt the link.
+ * @return the current {@link NetworkSpecifierBuilder} builder, enabling chaining of builder
+ * methods.
+ */
+ public @NonNull NetworkSpecifierBuilder setPskPassphrase(@NonNull String pskPassphrase) {
+ if (!WifiAwareUtils.validatePassphrase(pskPassphrase)) {
+ throw new IllegalArgumentException("Passphrase must meet length requirements");
+ }
+ mPskPassphrase = pskPassphrase;
+ return this;
+ }
+
+ /**
+ * Configure the PMK for the Wi-Fi Aware connection being requested. This method
+ * is optional - if not called, then an Open (unencrypted) connection will be created.
+ *
+ * @param pmk A PMK (pairwise master key, see IEEE 802.11i) specifying the key to use for
+ * encrypting the data-path. Use the {@link #setPskPassphrase(String)} to
+ * specify a Passphrase.
+ * @return the current {@link NetworkSpecifierBuilder} builder, enabling chaining of builder
+ * methods.
+ * @hide
+ */
+ @SystemApi
+ public @NonNull NetworkSpecifierBuilder setPmk(@NonNull byte[] pmk) {
+ if (!WifiAwareUtils.validatePmk(pmk)) {
+ throw new IllegalArgumentException("PMK must 32 bytes");
+ }
+ mPmk = pmk;
+ return this;
+ }
+
+ /**
+ * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)}
+ * for a WiFi Aware connection (link) to the specified peer. The
+ * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
+ * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
+ * <p> The default builder constructor will initialize a NetworkSpecifier which requests an
+ * open (non-encrypted) link. To request an encrypted link use the
+ * {@link #setPskPassphrase(String)} builder method.
+ *
+ * @return A {@link NetworkSpecifier} to be used to construct
+ * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass
+ * to {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
+ * android.net.ConnectivityManager.NetworkCallback)}
+ * [or other varieties of that API].
+ */
+ public @NonNull NetworkSpecifier build() {
+ if (mDiscoverySession == null) {
+ throw new IllegalStateException("Null discovery session!?");
+ }
+ if (mPskPassphrase != null & mPmk != null) {
+ throw new IllegalStateException(
+ "Can only specify a Passphrase or a PMK - not both!");
+ }
+
+ int role = mDiscoverySession instanceof SubscribeDiscoverySession
+ ? WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
+ : WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER;
+
+ if (role == WIFI_AWARE_DATA_PATH_ROLE_INITIATOR && mPeerHandle == null) {
+ throw new IllegalStateException("Null peerHandle!?");
+ }
+
+ return new WifiAwareNetworkSpecifier(
+ WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB, role,
+ mDiscoverySession.mClientId, mDiscoverySession.mSessionId, mPeerHandle.peerId,
+ null, mPmk, mPskPassphrase, Process.myUid());
+ }
+ }
}
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareNetworkInfo.java b/wifi/java/android/net/wifi/aware/WifiAwareNetworkInfo.java
new file mode 100644
index 000000000000..0f29e081e2a0
--- /dev/null
+++ b/wifi/java/android/net/wifi/aware/WifiAwareNetworkInfo.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.aware;
+
+import android.annotation.Nullable;
+import android.net.NetworkCapabilities;
+import android.net.TransportInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.net.Inet6Address;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.Objects;
+
+/**
+ * Wi-Fi Aware-specific network information. The information can be extracted from the
+ * {@link android.net.NetworkCapabilities} of the network using
+ * {@link NetworkCapabilities#getTransportInfo()}.
+ * The {@link NetworkCapabilities} is provided by the connectivity service to apps, e.g. received
+ * through the
+ * {@link android.net.ConnectivityManager.NetworkCallback#onCapabilitiesChanged(android.net.Network,
+ * android.net.NetworkCapabilities)} callback.
+ * <p>
+ * The Wi-Fi Aware-specific network information include the peer's scoped link-local IPv6 address
+ * for the Wi-Fi Aware link. The scoped link-local IPv6 can then be used to create a
+ * {@link java.net.Socket} connection to the peer.
+ */
+public final class WifiAwareNetworkInfo implements TransportInfo, Parcelable {
+ private Inet6Address mIpv6Addr;
+
+ /** @hide */
+ public WifiAwareNetworkInfo(Inet6Address ipv6Addr) {
+ mIpv6Addr = ipv6Addr;
+ }
+
+ /**
+ * Get the scoped link-local IPv6 address of the Wi-Fi Aware peer (not of the local device!).
+ *
+ * @return An IPv6 address.
+ */
+ @Nullable
+ public Inet6Address getPeerIpv6Addr() {
+ return mIpv6Addr;
+ }
+
+ // parcelable methods
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeByteArray(mIpv6Addr.getAddress());
+ NetworkInterface ni = mIpv6Addr.getScopedInterface();
+ dest.writeString(ni == null ? null : ni.getName());
+ }
+
+ public static final Creator<WifiAwareNetworkInfo> CREATOR =
+ new Creator<WifiAwareNetworkInfo>() {
+ @Override
+ public WifiAwareNetworkInfo createFromParcel(Parcel in) {
+ Inet6Address ipv6Addr;
+ try {
+ byte[] addr = in.createByteArray();
+ String interfaceName = in.readString();
+ NetworkInterface ni = null;
+ if (interfaceName != null) {
+ try {
+ ni = NetworkInterface.getByName(interfaceName);
+ } catch (SocketException e) {
+ e.printStackTrace();
+ }
+ }
+ ipv6Addr = Inet6Address.getByAddress(null, addr, ni);
+ } catch (UnknownHostException e) {
+ e.printStackTrace();
+ return null;
+ }
+
+ return new WifiAwareNetworkInfo(ipv6Addr);
+ }
+
+ @Override
+ public WifiAwareNetworkInfo[] newArray(int size) {
+ return new WifiAwareNetworkInfo[size];
+ }
+ };
+
+
+ // object methods
+
+ @Override
+ public String toString() {
+ return new StringBuilder("AwareNetworkInfo: IPv6=").append(mIpv6Addr).toString();
+ }
+
+ /** @hide */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof WifiAwareNetworkInfo)) {
+ return false;
+ }
+
+ WifiAwareNetworkInfo lhs = (WifiAwareNetworkInfo) obj;
+ return Objects.equals(mIpv6Addr, lhs.mIpv6Addr);
+ }
+
+ /** @hide */
+ @Override
+ public int hashCode() {
+ return Objects.hash(mIpv6Addr);
+ }
+}
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareSession.java b/wifi/java/android/net/wifi/aware/WifiAwareSession.java
index 321965330e0b..5f8841cb0148 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareSession.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareSession.java
@@ -213,7 +213,7 @@ public class WifiAwareSession implements AutoCloseable {
* This API is targeted for applications which can obtain the peer MAC address using OOB
* (out-of-band) discovery. Aware discovery does not provide the MAC address of the peer -
* when using Aware discovery use the alternative network specifier method -
- * {@link DiscoverySession#createNetworkSpecifierOpen(PeerHandle)}.
+ * {@link android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder}.
* <p>
* To set up an encrypted link use the
* {@link #createNetworkSpecifierPassphrase(int, byte[], String)} API.
@@ -254,7 +254,7 @@ public class WifiAwareSession implements AutoCloseable {
* This API is targeted for applications which can obtain the peer MAC address using OOB
* (out-of-band) discovery. Aware discovery does not provide the MAC address of the peer -
* when using Aware discovery use the alternative network specifier method -
- * {@link DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String)}.
+ * {@link android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder}.
*
* @param role The role of this device:
* {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_INITIATOR} or
@@ -300,7 +300,7 @@ public class WifiAwareSession implements AutoCloseable {
* This API is targeted for applications which can obtain the peer MAC address using OOB
* (out-of-band) discovery. Aware discovery does not provide the MAC address of the peer -
* when using Aware discovery use the alternative network specifier method -
- * {@link DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String)}.
+ * {@link android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder}.
*
* @param role The role of this device:
* {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_INITIATOR} or
diff --git a/wifi/java/android/net/wifi/hotspot2/OsuProvider.java b/wifi/java/android/net/wifi/hotspot2/OsuProvider.java
index 893b19ce3a3b..6d82ca152202 100644
--- a/wifi/java/android/net/wifi/hotspot2/OsuProvider.java
+++ b/wifi/java/android/net/wifi/hotspot2/OsuProvider.java
@@ -19,13 +19,16 @@ package android.net.wifi.hotspot2;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.net.wifi.WifiSsid;
+import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import java.util.ArrayList;
-import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
+import java.util.Map;
import java.util.Objects;
/**
@@ -52,9 +55,9 @@ public final class OsuProvider implements Parcelable {
private WifiSsid mOsuSsid;
/**
- * Friendly name of the OSU provider.
+ * Map of friendly names expressed as different language for the OSU provider.
*/
- private final String mFriendlyName;
+ private final Map<String, String> mFriendlyNames;
/**
* Description of the OSU provider.
@@ -81,10 +84,11 @@ public final class OsuProvider implements Parcelable {
*/
private final Icon mIcon;
- public OsuProvider(WifiSsid osuSsid, String friendlyName, String serviceDescription,
- Uri serverUri, String nai, List<Integer> methodList, Icon icon) {
+ public OsuProvider(WifiSsid osuSsid, Map<String, String> friendlyNames,
+ String serviceDescription, Uri serverUri, String nai, List<Integer> methodList,
+ Icon icon) {
mOsuSsid = osuSsid;
- mFriendlyName = friendlyName;
+ mFriendlyNames = friendlyNames;
mServiceDescription = serviceDescription;
mServerUri = serverUri;
mNetworkAccessIdentifier = nai;
@@ -104,7 +108,7 @@ public final class OsuProvider implements Parcelable {
public OsuProvider(OsuProvider source) {
if (source == null) {
mOsuSsid = null;
- mFriendlyName = null;
+ mFriendlyNames = null;
mServiceDescription = null;
mServerUri = null;
mNetworkAccessIdentifier = null;
@@ -114,7 +118,7 @@ public final class OsuProvider implements Parcelable {
}
mOsuSsid = source.mOsuSsid;
- mFriendlyName = source.mFriendlyName;
+ mFriendlyNames = source.mFriendlyNames;
mServiceDescription = source.mServiceDescription;
mServerUri = source.mServerUri;
mNetworkAccessIdentifier = source.mNetworkAccessIdentifier;
@@ -134,8 +138,32 @@ public final class OsuProvider implements Parcelable {
mOsuSsid = osuSsid;
}
+ /**
+ * Return the friendly Name for current language from the list of friendly names of OSU
+ * provider.
+ *
+ * The string matching the default locale will be returned if it is found, otherwise the string
+ * in english or the first string in the list will be returned if english is not found.
+ * A null will be returned if the list is empty.
+ *
+ * @return String matching the default locale, null otherwise
+ */
public String getFriendlyName() {
- return mFriendlyName;
+ if (mFriendlyNames == null || mFriendlyNames.isEmpty()) return null;
+ String lang = Locale.getDefault().getLanguage();
+ String friendlyName = mFriendlyNames.get(lang);
+ if (friendlyName != null) {
+ return friendlyName;
+ }
+ friendlyName = mFriendlyNames.get("en");
+ if (friendlyName != null) {
+ return friendlyName;
+ }
+ return mFriendlyNames.get(mFriendlyNames.keySet().stream().findFirst().get());
+ }
+
+ public Map<String, String> getFriendlyNameList() {
+ return mFriendlyNames;
}
public String getServiceDescription() {
@@ -151,7 +179,7 @@ public final class OsuProvider implements Parcelable {
}
public List<Integer> getMethodList() {
- return Collections.unmodifiableList(mMethodList);
+ return mMethodList;
}
public Icon getIcon() {
@@ -166,12 +194,14 @@ public final class OsuProvider implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(mOsuSsid, flags);
- dest.writeString(mFriendlyName);
dest.writeString(mServiceDescription);
dest.writeParcelable(mServerUri, flags);
dest.writeString(mNetworkAccessIdentifier);
dest.writeList(mMethodList);
dest.writeParcelable(mIcon, flags);
+ Bundle bundle = new Bundle();
+ bundle.putSerializable("friendlyNameMap", (HashMap<String, String>) mFriendlyNames);
+ dest.writeBundle(bundle);
}
@Override
@@ -184,7 +214,8 @@ public final class OsuProvider implements Parcelable {
}
OsuProvider that = (OsuProvider) thatObject;
return (mOsuSsid == null ? that.mOsuSsid == null : mOsuSsid.equals(that.mOsuSsid))
- && TextUtils.equals(mFriendlyName, that.mFriendlyName)
+ && (mFriendlyNames == null) ? that.mFriendlyNames == null
+ : mFriendlyNames.equals(that.mFriendlyNames)
&& TextUtils.equals(mServiceDescription, that.mServiceDescription)
&& (mServerUri == null ? that.mServerUri == null
: mServerUri.equals(that.mServerUri))
@@ -196,14 +227,15 @@ public final class OsuProvider implements Parcelable {
@Override
public int hashCode() {
- return Objects.hash(mOsuSsid, mFriendlyName, mServiceDescription, mServerUri,
- mNetworkAccessIdentifier, mMethodList, mIcon);
+ // mIcon is not hashable, skip the variable.
+ return Objects.hash(mOsuSsid, mServiceDescription, mFriendlyNames,
+ mServerUri, mNetworkAccessIdentifier, mMethodList);
}
@Override
public String toString() {
return "OsuProvider{mOsuSsid=" + mOsuSsid
- + " mFriendlyName=" + mFriendlyName
+ + " mFriendlyNames=" + mFriendlyNames
+ " mServiceDescription=" + mServiceDescription
+ " mServerUri=" + mServerUri
+ " mNetworkAccessIdentifier=" + mNetworkAccessIdentifier
@@ -212,20 +244,22 @@ public final class OsuProvider implements Parcelable {
}
public static final Creator<OsuProvider> CREATOR =
- new Creator<OsuProvider>() {
- @Override
- public OsuProvider createFromParcel(Parcel in) {
- WifiSsid osuSsid = (WifiSsid) in.readParcelable(null);
- String friendlyName = in.readString();
- String serviceDescription = in.readString();
- Uri serverUri = (Uri) in.readParcelable(null);
- String nai = in.readString();
- List<Integer> methodList = new ArrayList<>();
- in.readList(methodList, null);
- Icon icon = (Icon) in.readParcelable(null);
- return new OsuProvider(osuSsid, friendlyName, serviceDescription, serverUri,
- nai, methodList, icon);
- }
+ new Creator<OsuProvider>() {
+ @Override
+ public OsuProvider createFromParcel(Parcel in) {
+ WifiSsid osuSsid = in.readParcelable(null);
+ String serviceDescription = in.readString();
+ Uri serverUri = in.readParcelable(null);
+ String nai = in.readString();
+ List<Integer> methodList = new ArrayList<>();
+ in.readList(methodList, null);
+ Icon icon = in.readParcelable(null);
+ Bundle bundle = in.readBundle();
+ Map<String, String> friendlyNamesMap = (HashMap) bundle.getSerializable(
+ "friendlyNameMap");
+ return new OsuProvider(osuSsid, friendlyNamesMap, serviceDescription,
+ serverUri, nai, methodList, icon);
+ }
@Override
public OsuProvider[] newArray(int size) {
diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
index 26bdb181e4a1..f09d8647e40e 100644
--- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
+++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
@@ -20,6 +20,7 @@ import android.net.wifi.hotspot2.pps.Credential;
import android.net.wifi.hotspot2.pps.HomeSp;
import android.net.wifi.hotspot2.pps.Policy;
import android.net.wifi.hotspot2.pps.UpdateParameter;
+import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -30,6 +31,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
+import java.util.Locale;
import java.util.Map;
import java.util.Objects;
@@ -324,6 +326,50 @@ public final class PasspointConfiguration implements Parcelable {
}
/**
+ * The map of OSU service provider names whose each element is presented in different
+ * languages for the service provider, which is used for finding a matching
+ * PasspointConfiguration with a given service provider name.
+ */
+ private Map<String, String> mServiceFriendlyNames = null;
+
+ /**
+ * @hide
+ */
+ public void setServiceFriendlyNames(Map<String, String> serviceFriendlyNames) {
+ mServiceFriendlyNames = serviceFriendlyNames;
+ }
+
+ /**
+ * @hide
+ */
+ public Map<String, String> getServiceFriendlyNames() {
+ return mServiceFriendlyNames;
+ }
+
+ /**
+ * Return the friendly Name for current language from the list of friendly names of OSU
+ * provider.
+ * The string matching the default locale will be returned if it is found, otherwise the
+ * first string in the list will be returned. A null will be returned if the list is empty.
+ *
+ * @return String matching the default locale, null otherwise
+ * @hide
+ */
+ public String getServiceFriendlyName() {
+ if (mServiceFriendlyNames == null || mServiceFriendlyNames.isEmpty()) return null;
+ String lang = Locale.getDefault().getLanguage();
+ String friendlyName = mServiceFriendlyNames.get(lang);
+ if (friendlyName != null) {
+ return friendlyName;
+ }
+ friendlyName = mServiceFriendlyNames.get("en");
+ if (friendlyName != null) {
+ return friendlyName;
+ }
+ return mServiceFriendlyNames.get(mServiceFriendlyNames.keySet().stream().findFirst().get());
+ }
+
+ /**
* Constructor for creating PasspointConfiguration with default values.
*/
public PasspointConfiguration() {}
@@ -362,6 +408,7 @@ public final class PasspointConfiguration implements Parcelable {
mUsageLimitStartTimeInMillis = source.mUsageLimitStartTimeInMillis;
mUsageLimitTimeLimitInMinutes = source.mUsageLimitTimeLimitInMinutes;
mUsageLimitUsageTimePeriodInMinutes = source.mUsageLimitUsageTimePeriodInMinutes;
+ mServiceFriendlyNames = source.mServiceFriendlyNames;
}
@Override
@@ -385,6 +432,10 @@ public final class PasspointConfiguration implements Parcelable {
dest.writeLong(mUsageLimitStartTimeInMillis);
dest.writeLong(mUsageLimitDataLimit);
dest.writeLong(mUsageLimitTimeLimitInMinutes);
+ Bundle bundle = new Bundle();
+ bundle.putSerializable("serviceFriendlyNames",
+ (HashMap<String, String>) mServiceFriendlyNames);
+ dest.writeBundle(bundle);
}
@Override
@@ -398,10 +449,10 @@ public final class PasspointConfiguration implements Parcelable {
PasspointConfiguration that = (PasspointConfiguration) thatObject;
return (mHomeSp == null ? that.mHomeSp == null : mHomeSp.equals(that.mHomeSp))
&& (mCredential == null ? that.mCredential == null
- : mCredential.equals(that.mCredential))
+ : mCredential.equals(that.mCredential))
&& (mPolicy == null ? that.mPolicy == null : mPolicy.equals(that.mPolicy))
&& (mSubscriptionUpdate == null ? that.mSubscriptionUpdate == null
- : mSubscriptionUpdate.equals(that.mSubscriptionUpdate))
+ : mSubscriptionUpdate.equals(that.mSubscriptionUpdate))
&& isTrustRootCertListEquals(mTrustRootCertList, that.mTrustRootCertList)
&& mUpdateIdentifier == that.mUpdateIdentifier
&& mCredentialPriority == that.mCredentialPriority
@@ -411,7 +462,9 @@ public final class PasspointConfiguration implements Parcelable {
&& mUsageLimitUsageTimePeriodInMinutes == that.mUsageLimitUsageTimePeriodInMinutes
&& mUsageLimitStartTimeInMillis == that.mUsageLimitStartTimeInMillis
&& mUsageLimitDataLimit == that.mUsageLimitDataLimit
- && mUsageLimitTimeLimitInMinutes == that.mUsageLimitTimeLimitInMinutes;
+ && mUsageLimitTimeLimitInMinutes == that.mUsageLimitTimeLimitInMinutes
+ && (mServiceFriendlyNames == null ? that.mServiceFriendlyNames == null
+ : mServiceFriendlyNames.equals(that.mServiceFriendlyNames));
}
@Override
@@ -419,7 +472,8 @@ public final class PasspointConfiguration implements Parcelable {
return Objects.hash(mHomeSp, mCredential, mPolicy, mSubscriptionUpdate, mTrustRootCertList,
mUpdateIdentifier, mCredentialPriority, mSubscriptionCreationTimeInMillis,
mSubscriptionExpirationTimeInMillis, mUsageLimitUsageTimePeriodInMinutes,
- mUsageLimitStartTimeInMillis, mUsageLimitDataLimit, mUsageLimitTimeLimitInMinutes);
+ mUsageLimitStartTimeInMillis, mUsageLimitDataLimit, mUsageLimitTimeLimitInMinutes,
+ mServiceFriendlyNames);
}
@Override
@@ -463,6 +517,9 @@ public final class PasspointConfiguration implements Parcelable {
builder.append("TrustRootCertServers: ").append(mTrustRootCertList.keySet())
.append("\n");
}
+ if (mServiceFriendlyNames != null) {
+ builder.append("ServiceFriendlyNames: ").append(mServiceFriendlyNames);
+ }
return builder.toString();
}
@@ -562,6 +619,10 @@ public final class PasspointConfiguration implements Parcelable {
config.setUsageLimitStartTimeInMillis(in.readLong());
config.setUsageLimitDataLimit(in.readLong());
config.setUsageLimitTimeLimitInMinutes(in.readLong());
+ Bundle bundle = in.readBundle();
+ Map<String, String> friendlyNamesMap = (HashMap) bundle.getSerializable(
+ "serviceFriendlyNames");
+ config.setServiceFriendlyNames(friendlyNamesMap);
return config;
}
diff --git a/wifi/java/com/android/server/wifi/AbstractWifiService.java b/wifi/java/com/android/server/wifi/AbstractWifiService.java
index 36f66aa81661..e94b9e6c8671 100644
--- a/wifi/java/com/android/server/wifi/AbstractWifiService.java
+++ b/wifi/java/com/android/server/wifi/AbstractWifiService.java
@@ -39,6 +39,7 @@ import android.os.ResultReceiver;
import android.os.WorkSource;
import java.util.List;
+import java.util.Map;
/**
* Abstract class implementing IWifiManager with stub methods throwing runtime exceptions.
@@ -127,6 +128,12 @@ public abstract class AbstractWifiService extends IWifiManager.Stub {
}
@Override
+ public Map<OsuProvider, PasspointConfiguration> getMatchingPasspointConfigsForOsuProviders(
+ List<OsuProvider> osuProviders) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public int addOrUpdateNetwork(WifiConfiguration config, String packageName) {
throw new UnsupportedOperationException();
}
diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
index 272f72721747..45e17201bd1f 100644
--- a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
@@ -31,6 +31,7 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.net.MacAddress;
import android.net.wifi.RttManager;
import android.os.Build;
import android.os.Handler;
@@ -50,6 +51,8 @@ import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.net.Inet6Address;
+import java.net.UnknownHostException;
import java.util.List;
/**
@@ -105,7 +108,7 @@ public class WifiAwareManagerTest {
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mockApplicationInfo.targetSdkVersion = Build.VERSION_CODES.P;
+ mockApplicationInfo.targetSdkVersion = Build.VERSION_CODES.Q;
when(mockPackageManager.getApplicationInfo(anyString(), anyInt())).thenReturn(
mockApplicationInfo);
when(mockContext.getOpPackageName()).thenReturn("XXX");
@@ -915,6 +918,8 @@ public class WifiAwareManagerTest {
final ConfigRequest configRequest = new ConfigRequest.Builder().build();
final PublishConfig publishConfig = new PublishConfig.Builder().build();
+ mockApplicationInfo.targetSdkVersion = Build.VERSION_CODES.P;
+
ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass(
WifiAwareSession.class);
ArgumentCaptor<IWifiAwareEventCallback> clientProxyCallback = ArgumentCaptor
@@ -948,6 +953,9 @@ public class WifiAwareManagerTest {
WifiAwareNetworkSpecifier ns =
(WifiAwareNetworkSpecifier) publishSession.getValue().createNetworkSpecifierOpen(
peerHandle);
+ WifiAwareNetworkSpecifier nsb = (WifiAwareNetworkSpecifier) new WifiAwareManager
+ .NetworkSpecifierBuilder().setDiscoverySession(publishSession.getValue())
+ .setPeerHandle(peerHandle).build();
// validate format
collector.checkThat("role", role, equalTo(ns.role));
@@ -955,9 +963,18 @@ public class WifiAwareManagerTest {
collector.checkThat("session_id", sessionId, equalTo(ns.sessionId));
collector.checkThat("peer_id", peerHandle.peerId, equalTo(ns.peerId));
+ collector.checkThat("role", role, equalTo(nsb.role));
+ collector.checkThat("client_id", clientId, equalTo(nsb.clientId));
+ collector.checkThat("session_id", sessionId, equalTo(nsb.sessionId));
+ collector.checkThat("peer_id", peerHandle.peerId, equalTo(nsb.peerId));
+
// (4) request an encrypted (PMK) network specifier from the session
ns = (WifiAwareNetworkSpecifier) publishSession.getValue().createNetworkSpecifierPmk(
peerHandle, pmk);
+ nsb =
+ (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder()
+ .setDiscoverySession(
+ publishSession.getValue()).setPeerHandle(peerHandle).setPmk(pmk).build();
// validate format
collector.checkThat("role", role, equalTo(ns.role));
@@ -966,9 +983,18 @@ public class WifiAwareManagerTest {
collector.checkThat("peer_id", peerHandle.peerId, equalTo(ns.peerId));
collector.checkThat("pmk", pmk , equalTo(ns.pmk));
+ collector.checkThat("role", role, equalTo(nsb.role));
+ collector.checkThat("client_id", clientId, equalTo(nsb.clientId));
+ collector.checkThat("session_id", sessionId, equalTo(nsb.sessionId));
+ collector.checkThat("peer_id", peerHandle.peerId, equalTo(nsb.peerId));
+ collector.checkThat("pmk", pmk , equalTo(nsb.pmk));
+
// (5) request an encrypted (Passphrase) network specifier from the session
ns = (WifiAwareNetworkSpecifier) publishSession.getValue().createNetworkSpecifierPassphrase(
peerHandle, passphrase);
+ nsb = (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder()
+ .setDiscoverySession(publishSession.getValue()).setPeerHandle(peerHandle)
+ .setPskPassphrase(passphrase).build();
// validate format
collector.checkThat("role", role, equalTo(ns.role));
@@ -977,6 +1003,12 @@ public class WifiAwareManagerTest {
collector.checkThat("peer_id", peerHandle.peerId, equalTo(ns.peerId));
collector.checkThat("passphrase", passphrase, equalTo(ns.passphrase));
+ collector.checkThat("role", role, equalTo(nsb.role));
+ collector.checkThat("client_id", clientId, equalTo(nsb.clientId));
+ collector.checkThat("session_id", sessionId, equalTo(nsb.sessionId));
+ collector.checkThat("peer_id", peerHandle.peerId, equalTo(nsb.peerId));
+ collector.checkThat("passphrase", passphrase, equalTo(nsb.passphrase));
+
verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService,
mockPublishSession, mockRttListener);
}
@@ -1048,7 +1080,7 @@ public class WifiAwareManagerTest {
*/
@Test(expected = IllegalArgumentException.class)
public void testNetworkSpecifierWithClientNullPmk() throws Exception {
- executeNetworkSpecifierWithClient(new PeerHandle(123412), true, null, null);
+ executeNetworkSpecifierWithClient(new PeerHandle(123412), true, null, null, false);
}
/**
@@ -1056,7 +1088,7 @@ public class WifiAwareManagerTest {
*/
@Test(expected = IllegalArgumentException.class)
public void testNetworkSpecifierWithClientIncorrectLengthPmk() throws Exception {
- executeNetworkSpecifierWithClient(new PeerHandle(123412), true, PMK_INVALID, null);
+ executeNetworkSpecifierWithClient(new PeerHandle(123412), true, PMK_INVALID, null, false);
}
/**
@@ -1064,7 +1096,7 @@ public class WifiAwareManagerTest {
*/
@Test(expected = IllegalArgumentException.class)
public void testNetworkSpecifierWithClientNullPassphrase() throws Exception {
- executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null, null);
+ executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null, null, false);
}
/**
@@ -1073,7 +1105,7 @@ public class WifiAwareManagerTest {
@Test(expected = IllegalArgumentException.class)
public void testNetworkSpecifierWithClientTooShortPassphrase() throws Exception {
executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null,
- PASSPHRASE_TOO_SHORT);
+ PASSPHRASE_TOO_SHORT, false);
}
/**
@@ -1081,7 +1113,8 @@ public class WifiAwareManagerTest {
*/
@Test(expected = IllegalArgumentException.class)
public void testNetworkSpecifierWithClientTooLongPassphrase() throws Exception {
- executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null, PASSPHRASE_TOO_LONG);
+ executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null, PASSPHRASE_TOO_LONG,
+ false);
}
/**
@@ -1089,7 +1122,8 @@ public class WifiAwareManagerTest {
*/
@Test(expected = IllegalArgumentException.class)
public void testNetworkSpecifierWithClientNullPeer() throws Exception {
- executeNetworkSpecifierWithClient(null, false, null, PASSPHRASE_VALID);
+ mockApplicationInfo.targetSdkVersion = Build.VERSION_CODES.P;
+ executeNetworkSpecifierWithClient(null, false, null, PASSPHRASE_VALID, false);
}
/**
@@ -1098,11 +1132,75 @@ public class WifiAwareManagerTest {
@Test
public void testNetworkSpecifierWithClientNullPeerLegacyApi() throws Exception {
mockApplicationInfo.targetSdkVersion = Build.VERSION_CODES.O;
- executeNetworkSpecifierWithClient(null, false, null, PASSPHRASE_VALID);
+ executeNetworkSpecifierWithClient(null, false, null, PASSPHRASE_VALID, false);
+ }
+
+ /**
+ * Validate that a null PMK triggers an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testNetworkSpecifierWithClientNullPmkBuilder() throws Exception {
+ executeNetworkSpecifierWithClient(new PeerHandle(123412), true, null, null, true);
+ }
+
+ /**
+ * Validate that a non-32-bytes PMK triggers an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testNetworkSpecifierWithClientIncorrectLengthPmkBuilder() throws Exception {
+ executeNetworkSpecifierWithClient(new PeerHandle(123412), true, PMK_INVALID, null, true);
+ }
+
+ /**
+ * Validate that a null Passphrase triggers an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testNetworkSpecifierWithClientNullPassphraseBuilder() throws Exception {
+ executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null, null, true);
+ }
+
+ /**
+ * Validate that a too short Passphrase triggers an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testNetworkSpecifierWithClientTooShortPassphraseBuilder() throws Exception {
+ executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null,
+ PASSPHRASE_TOO_SHORT, true);
+ }
+
+ /**
+ * Validate that a too long Passphrase triggers an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testNetworkSpecifierWithClientTooLongPassphraseBuilder() throws Exception {
+ executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null, PASSPHRASE_TOO_LONG,
+ true);
+ }
+
+ /**
+ * Validate that a null PeerHandle triggers an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testNetworkSpecifierWithClientNullPeerBuilder() throws Exception {
+ executeNetworkSpecifierWithClient(null, false, null, PASSPHRASE_VALID, true);
+ }
+
+ /**
+ * Validate that a null PeerHandle does not trigger an exception for legacy API.
+ */
+ @Test
+ public void testNetworkSpecifierWithClientNullPeerLegacyApiBuilder() throws Exception {
+ mockApplicationInfo.targetSdkVersion = Build.VERSION_CODES.O;
+ executeNetworkSpecifierWithClient(null, false, null, PASSPHRASE_VALID, false);
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testNetworkSpecifierDeprecatedOnNewApi() throws Exception {
+ executeNetworkSpecifierWithClient(null, false, null, PASSPHRASE_VALID, false);
}
private void executeNetworkSpecifierWithClient(PeerHandle peerHandle, boolean doPmk, byte[] pmk,
- String passphrase) throws Exception {
+ String passphrase, boolean useBuilder) throws Exception {
final int clientId = 4565;
final int sessionId = 123;
final ConfigRequest configRequest = new ConfigRequest.Builder().build();
@@ -1139,9 +1237,20 @@ public class WifiAwareManagerTest {
// (3) create network specifier
if (doPmk) {
- publishSession.getValue().createNetworkSpecifierPmk(peerHandle, pmk);
+ if (useBuilder) {
+ new WifiAwareManager.NetworkSpecifierBuilder().setDiscoverySession(
+ publishSession.getValue()).setPeerHandle(peerHandle).setPmk(pmk).build();
+ } else {
+ publishSession.getValue().createNetworkSpecifierPmk(peerHandle, pmk);
+ }
} else {
- publishSession.getValue().createNetworkSpecifierPassphrase(peerHandle, passphrase);
+ if (useBuilder) {
+ new WifiAwareManager.NetworkSpecifierBuilder().setDiscoverySession(
+ publishSession.getValue()).setPeerHandle(peerHandle).setPskPassphrase(
+ passphrase).build();
+ } else {
+ publishSession.getValue().createNetworkSpecifierPassphrase(peerHandle, passphrase);
+ }
}
}
@@ -1245,4 +1354,31 @@ public class WifiAwareManagerTest {
sessionCaptor.getValue().createNetworkSpecifierPassphrase(role, someMac, passphrase);
}
}
+
+ // WifiAwareNetworkInfo tests
+
+ @Test
+ public void testWifiAwareNetworkCapabilitiesParcel() throws UnknownHostException {
+ final Inet6Address inet6 = MacAddress.fromString(
+ "11:22:33:44:55:66").getLinkLocalIpv6FromEui48Mac();
+ // note: dummy scope = 5
+ final Inet6Address inet6Scoped = Inet6Address.getByAddress(null, inet6.getAddress(), 5);
+
+ assertEquals(inet6Scoped.toString(), "/fe80::1322:33ff:fe44:5566%5");
+ WifiAwareNetworkInfo cap = new WifiAwareNetworkInfo(inet6Scoped);
+
+ Parcel parcelW = Parcel.obtain();
+ cap.writeToParcel(parcelW, 0);
+ byte[] bytes = parcelW.marshall();
+ parcelW.recycle();
+
+ Parcel parcelR = Parcel.obtain();
+ parcelR.unmarshall(bytes, 0, bytes.length);
+ parcelR.setDataPosition(0);
+ WifiAwareNetworkInfo rereadCap =
+ WifiAwareNetworkInfo.CREATOR.createFromParcel(parcelR);
+
+ assertEquals(cap.getPeerIpv6Addr().toString(), "/fe80::1322:33ff:fe44:5566%5");
+ assertEquals(cap.hashCode(), rereadCap.hashCode());
+ }
}
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/OsuProviderTest.java b/wifi/tests/src/android/net/wifi/hotspot2/OsuProviderTest.java
index d3f91f06da61..89ecd0fd561b 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/OsuProviderTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/OsuProviderTest.java
@@ -28,9 +28,10 @@ import android.support.test.filters.SmallTest;
import org.junit.Test;
import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* Unit tests for {@link android.net.wifi.hotspot2.OsuProvider}.
@@ -40,6 +41,15 @@ public class OsuProviderTest {
private static final WifiSsid TEST_SSID =
WifiSsid.createFromByteArray("TEST SSID".getBytes(StandardCharsets.UTF_8));
private static final String TEST_FRIENDLY_NAME = "Friendly Name";
+ private static final Map<String, String> TEST_FRIENDLY_NAMES =
+ new HashMap<String, String>() {
+ {
+ put("en", TEST_FRIENDLY_NAME);
+ put("kr", TEST_FRIENDLY_NAME + 2);
+ put("jp", TEST_FRIENDLY_NAME + 3);
+ }
+ };
+
private static final String TEST_SERVICE_DESCRIPTION = "Dummy Service";
private static final Uri TEST_SERVER_URI = Uri.parse("https://test.com");
private static final String TEST_NAI = "test.access.com";
@@ -59,7 +69,9 @@ public class OsuProviderTest {
parcel.setDataPosition(0); // Rewind data position back to the beginning for read.
OsuProvider readInfo = OsuProvider.CREATOR.createFromParcel(parcel);
+
assertEquals(writeInfo, readInfo);
+ assertEquals(writeInfo.hashCode(), readInfo.hashCode());
}
/**
@@ -79,8 +91,8 @@ public class OsuProviderTest {
*/
@Test
public void verifyParcelWithFullProviderInfo() throws Exception {
- verifyParcel(new OsuProvider(TEST_SSID, TEST_FRIENDLY_NAME, TEST_SERVICE_DESCRIPTION,
- TEST_SERVER_URI, TEST_NAI, TEST_METHOD_LIST, TEST_ICON));
+ verifyParcel(new OsuProvider(TEST_SSID, TEST_FRIENDLY_NAMES,
+ TEST_SERVICE_DESCRIPTION, TEST_SERVER_URI, TEST_NAI, TEST_METHOD_LIST, TEST_ICON));
}
/**
@@ -100,8 +112,8 @@ public class OsuProviderTest {
*/
@Test
public void verifyCopyConstructorWithValidSource() throws Exception {
- OsuProvider source = new OsuProvider(TEST_SSID, TEST_FRIENDLY_NAME, TEST_SERVICE_DESCRIPTION,
- TEST_SERVER_URI, TEST_NAI, TEST_METHOD_LIST, TEST_ICON);
+ OsuProvider source = new OsuProvider(TEST_SSID, TEST_FRIENDLY_NAMES,
+ TEST_SERVICE_DESCRIPTION, TEST_SERVER_URI, TEST_NAI, TEST_METHOD_LIST, TEST_ICON);
assertEquals(source, new OsuProvider(source));
}
@@ -112,10 +124,12 @@ public class OsuProviderTest {
*/
@Test
public void verifyGetters() throws Exception {
- OsuProvider provider = new OsuProvider(TEST_SSID, TEST_FRIENDLY_NAME,
+ OsuProvider provider = new OsuProvider(TEST_SSID, TEST_FRIENDLY_NAMES,
TEST_SERVICE_DESCRIPTION, TEST_SERVER_URI, TEST_NAI, TEST_METHOD_LIST, TEST_ICON);
+
assertTrue(TEST_SSID.equals(provider.getOsuSsid()));
assertTrue(TEST_FRIENDLY_NAME.equals(provider.getFriendlyName()));
+ assertTrue(TEST_FRIENDLY_NAMES.equals(provider.getFriendlyNameList()));
assertTrue(TEST_SERVICE_DESCRIPTION.equals(provider.getServiceDescription()));
assertTrue(TEST_SERVER_URI.equals(provider.getServerUri()));
assertTrue(TEST_NAI.equals(provider.getNetworkAccessIdentifier()));
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
index 775ce21656f7..ee5a75ebe1b8 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
@@ -166,6 +166,10 @@ public class PasspointConfigurationTest {
config.setUsageLimitStartTimeInMillis(124214213);
config.setUsageLimitDataLimit(14121);
config.setUsageLimitTimeLimitInMinutes(78912);
+ Map<String, String> friendlyNames = new HashMap<>();
+ friendlyNames.put("en", "ServiceName1");
+ friendlyNames.put("kr", "ServiceName2");
+ config.setServiceFriendlyNames(friendlyNames);
return config;
}
@@ -206,6 +210,18 @@ public class PasspointConfigurationTest {
}
/**
+ * Verify parcel read/write for a configuration that doesn't contain a list of service names.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyParcelWithoutServiceNames() throws Exception {
+ PasspointConfiguration config = createConfig();
+ config.setServiceFriendlyNames(null);
+ verifyParcel(config);
+ }
+
+ /**
* Verify parcel read/write for a configuration that doesn't contain HomeSP.
*
* @throws Exception