summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--StubLibraries.bp47
-rw-r--r--apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java101
-rw-r--r--apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl4
-rw-r--r--apex/blobstore/framework/java/android/app/blob/XmlTags.java1
-rw-r--r--apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java62
-rw-r--r--apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java7
-rw-r--r--apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java27
-rw-r--r--apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java2
-rw-r--r--api/current.txt28
-rwxr-xr-xapi/system-current.txt101
-rw-r--r--api/test-current.txt26
-rw-r--r--cmds/statsd/src/atoms.proto4
-rw-r--r--core/java/android/accessibilityservice/AccessibilityShortcutInfo.java37
-rw-r--r--core/java/android/app/ITaskOrganizerController.aidl6
-rw-r--r--core/java/android/app/PictureInPictureParams.java8
-rw-r--r--core/java/android/app/TaskInfo.java19
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java4
-rw-r--r--core/java/android/app/compat/TEST_MAPPING7
-rw-r--r--core/java/android/app/prediction/AppPredictionSessionId.java21
-rw-r--r--core/java/android/app/prediction/AppPredictor.java2
-rw-r--r--core/java/android/bluetooth/BluetoothA2dp.java34
-rwxr-xr-xcore/java/android/bluetooth/BluetoothA2dpSink.java8
-rw-r--r--core/java/android/bluetooth/BluetoothAdapter.java93
-rw-r--r--core/java/android/bluetooth/BluetoothPbap.java14
-rw-r--r--core/java/android/content/ContentResolver.java23
-rw-r--r--core/java/android/content/Intent.java11
-rw-r--r--core/java/android/content/integrity/AppInstallMetadata.java28
-rw-r--r--core/java/android/content/integrity/AtomicFormula.java9
-rw-r--r--core/java/android/content/integrity/InstallerAllowedByManifestFormula.java63
-rw-r--r--core/java/android/content/integrity/IntegrityFormula.java211
-rw-r--r--core/java/android/content/pm/InstallationFile.java80
-rw-r--r--core/java/android/content/pm/LauncherApps.java11
-rw-r--r--core/java/android/content/res/Configuration.java10
-rw-r--r--core/java/android/hardware/biometrics/BiometricConstants.java8
-rw-r--r--core/java/android/hardware/biometrics/BiometricFaceConstants.java93
-rw-r--r--core/java/android/hardware/biometrics/BiometricFingerprintConstants.java71
-rw-r--r--core/java/android/hardware/biometrics/BiometricManager.java12
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintManager.java7
-rw-r--r--core/java/android/os/incremental/IncrementalFileStorages.java10
-rw-r--r--core/java/android/os/incremental/IncrementalManager.java9
-rw-r--r--core/java/android/os/incremental/IncrementalStorage.java102
-rw-r--r--core/java/android/os/incremental/V4Signature.java62
-rw-r--r--core/java/android/provider/Settings.java3
-rw-r--r--core/java/android/service/dataloader/DataLoaderService.java2
-rw-r--r--core/java/android/view/AccessibilityEmbeddedConnection.java81
-rw-r--r--core/java/android/view/AccessibilityInteractionController.java32
-rw-r--r--core/java/android/view/IWindowManager.aidl2
-rw-r--r--core/java/android/view/InsetsSource.java2
-rw-r--r--core/java/android/view/Surface.java8
-rw-r--r--core/java/android/view/SurfaceView.java13
-rw-r--r--core/java/android/view/View.java12
-rw-r--r--core/java/android/view/ViewRootImpl.java33
-rw-r--r--core/java/android/view/WindowInsetsController.java4
-rw-r--r--core/java/android/view/WindowManagerGlobal.java20
-rw-r--r--core/java/android/view/accessibility/AccessibilityManager.java51
-rw-r--r--core/java/android/view/accessibility/IAccessibilityEmbeddedConnection.aidl32
-rw-r--r--core/java/android/view/accessibility/IAccessibilityManager.aidl6
-rw-r--r--core/java/android/view/textclassifier/TextClassificationSession.java30
-rw-r--r--core/java/android/view/textclassifier/TextClassificationSessionId.java68
-rw-r--r--core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java5
-rw-r--r--core/jni/LayoutlibLoader.cpp2
-rw-r--r--core/jni/android_os_incremental_IncrementalManager.cpp26
-rw-r--r--core/jni/com_android_internal_os_Zygote.cpp6
-rw-r--r--core/proto/android/content/locusid.proto27
-rw-r--r--core/proto/android/server/peopleservice.proto75
-rw-r--r--core/res/AndroidManifest.xml2
-rw-r--r--core/res/res/values/attrs.xml6
-rw-r--r--core/res/res/values/config.xml6
-rw-r--r--core/res/res/values/strings.xml8
-rw-r--r--core/res/res/values/symbols.xml4
-rw-r--r--core/tests/coretests/AndroidManifest.xml6
-rw-r--r--core/tests/coretests/res/values/strings.xml3
-rw-r--r--core/tests/coretests/res/xml/accessibility_shortcut_test_activity.xml2
-rw-r--r--core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java16
-rw-r--r--core/tests/coretests/src/android/content/ContentResolverTest.java9
-rw-r--r--core/tests/coretests/src/android/content/SlowProvider.java65
-rw-r--r--core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java105
-rw-r--r--core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java77
-rw-r--r--core/tests/coretests/src/android/content/res/ConfigurationTest.java9
-rw-r--r--core/tests/coretests/src/android/database/DatabaseGeneralTest.java174
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java1
-rw-r--r--keystore/java/android/security/keystore/KeyGenParameterSpec.java72
-rw-r--r--keystore/java/android/security/keystore/KeyInfo.java19
-rw-r--r--keystore/java/android/security/keystore/KeyProperties.java21
-rw-r--r--keystore/java/android/security/keystore/KeyProtection.java59
-rw-r--r--keystore/java/android/security/keystore/KeymasterUtils.java19
-rw-r--r--keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java3
-rw-r--r--keystore/java/android/security/keystore/UserAuthArgs.java1
-rw-r--r--libs/androidfw/AssetManager2.cpp29
-rw-r--r--libs/androidfw/tests/AssetManager2_test.cpp21
-rw-r--r--libs/androidfw/tests/data/lib_two/R.h12
-rw-r--r--libs/androidfw/tests/data/lib_two/lib_two.apkbin1426 -> 1586 bytes
-rw-r--r--libs/androidfw/tests/data/lib_two/res/values/values.xml11
-rw-r--r--libs/androidfw/tests/data/libclient/R.h1
-rw-r--r--libs/androidfw/tests/data/libclient/libclient.apkbin1982 -> 2168 bytes
-rw-r--r--libs/androidfw/tests/data/libclient/res/values/values.xml6
-rw-r--r--location/java/android/location/AbstractListenerManager.java129
-rw-r--r--location/java/android/location/ILocationManager.aidl8
-rw-r--r--location/java/android/location/LocationManager.java58
-rw-r--r--media/java/android/media/tv/tuner/Tuner.java10
-rw-r--r--media/tests/TunerTest/OWNERS4
-rw-r--r--packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java4
-rw-r--r--packages/SettingsLib/res/values/strings.xml12
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java43
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java29
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java20
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java48
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java54
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java4
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java2
-rw-r--r--packages/SystemUI/AndroidManifest.xml3
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java4
-rw-r--r--packages/SystemUI/res-keyguard/layout/controls_management.xml34
-rw-r--r--packages/SystemUI/res-keyguard/layout/controls_zone_header.xml28
-rw-r--r--packages/SystemUI/res/drawable/ic_cancel_24.xml25
-rw-r--r--packages/SystemUI/res/drawable/ic_important.xml26
-rw-r--r--packages/SystemUI/res/drawable/ic_important_outline.xml27
-rw-r--r--packages/SystemUI/res/drawable/ic_star.xml25
-rw-r--r--packages/SystemUI/res/drawable/ic_star_border.xml25
-rw-r--r--packages/SystemUI/res/drawable/screenshot_cancel.xml28
-rw-r--r--packages/SystemUI/res/layout/controls_base_item.xml4
-rw-r--r--packages/SystemUI/res/layout/controls_management.xml95
-rw-r--r--packages/SystemUI/res/layout/controls_management_apps.xml25
-rw-r--r--packages/SystemUI/res/layout/controls_management_favorites.xml92
-rw-r--r--packages/SystemUI/res/layout/controls_with_favorites.xml15
-rw-r--r--packages/SystemUI/res/layout/global_screenshot.xml14
-rw-r--r--packages/SystemUI/res/layout/notification_conversation_info.xml111
-rw-r--r--packages/SystemUI/res/values/dimens.xml28
-rw-r--r--packages/SystemUI/res/values/strings.xml28
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt110
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt181
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt111
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/FavoriteModel.kt153
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java235
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java39
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java182
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java35
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt110
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoriteModelTest.kt198
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java64
-rw-r--r--proto/src/system_messages.proto4
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java3
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java18
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java4
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java171
-rw-r--r--services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java28
-rw-r--r--services/backup/java/com/android/server/backup/UserBackupManagerService.java11
-rw-r--r--services/backup/java/com/android/server/backup/UserBackupPreferences.java13
-rw-r--r--services/backup/java/com/android/server/backup/internal/BackupHandler.java3
-rw-r--r--services/backup/java/com/android/server/backup/params/RestoreParams.java29
-rw-r--r--services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java9
-rw-r--r--services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java16
-rw-r--r--services/core/java/com/android/server/LocationManagerService.java20
-rw-r--r--services/core/java/com/android/server/LocationManagerServiceUtils.java12
-rw-r--r--services/core/java/com/android/server/UiModeManagerService.java17
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java27
-rw-r--r--services/core/java/com/android/server/am/AppErrors.java2
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java47
-rw-r--r--services/core/java/com/android/server/am/UserController.java1
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java4
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricService.java222
-rw-r--r--services/core/java/com/android/server/biometrics/LoggableMonitor.java23
-rw-r--r--services/core/java/com/android/server/biometrics/Utils.java13
-rw-r--r--services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java7
-rw-r--r--services/core/java/com/android/server/compat/TEST_MAPPING21
-rw-r--r--services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java11
-rw-r--r--services/core/java/com/android/server/integrity/model/ComponentBitSize.java3
-rw-r--r--services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java4
-rw-r--r--services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java4
-rw-r--r--services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java1
-rw-r--r--services/core/java/com/android/server/location/GnssMeasurementsProvider.java46
-rw-r--r--services/core/java/com/android/server/location/GnssNavigationMessageProvider.java2
-rw-r--r--services/core/java/com/android/server/location/GnssStatusListenerHelper.java3
-rw-r--r--services/core/java/com/android/server/location/RemoteListenerHelper.java23
-rw-r--r--services/core/java/com/android/server/location/SettingsHelper.java18
-rw-r--r--services/core/java/com/android/server/location/TEST_MAPPING9
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssManagerService.java56
-rw-r--r--services/core/java/com/android/server/pm/LauncherAppsService.java5
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java76
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java21
-rw-r--r--services/core/java/com/android/server/pm/UserSystemPackageInstaller.java2
-rw-r--r--services/core/java/com/android/server/power/PreRebootLogger.java133
-rw-r--r--services/core/java/com/android/server/power/ShutdownThread.java10
-rw-r--r--services/core/java/com/android/server/rollback/Rollback.java21
-rw-r--r--services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java66
-rw-r--r--services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java99
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java7
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java6
-rw-r--r--services/core/java/com/android/server/wm/DisplayAreaPolicy.java66
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java8
-rw-r--r--services/core/java/com/android/server/wm/InsetsPolicy.java40
-rw-r--r--services/core/java/com/android/server/wm/InsetsSourceProvider.java8
-rw-r--r--services/core/java/com/android/server/wm/InsetsStateController.java19
-rw-r--r--services/core/java/com/android/server/wm/Task.java18
-rw-r--r--services/core/java/com/android/server/wm/TaskOrganizerController.java42
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java21
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java69
-rw-r--r--services/incremental/IncrementalService.cpp2
-rw-r--r--services/incremental/IncrementalService.h1
-rw-r--r--services/people/java/com/android/server/people/data/AbstractProtoDiskReadWriter.java264
-rw-r--r--services/people/java/com/android/server/people/data/ConversationInfo.java74
-rw-r--r--services/people/java/com/android/server/people/data/ConversationStore.java251
-rw-r--r--services/people/java/com/android/server/people/data/DataManager.java17
-rw-r--r--services/people/java/com/android/server/people/data/PackageData.java25
-rw-r--r--services/people/java/com/android/server/people/data/UserData.java25
-rw-r--r--services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java136
-rw-r--r--services/tests/servicestests/src/com/android/server/backup/UserBackupPreferencesTest.java33
-rw-r--r--services/tests/servicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java44
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java45
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java18
-rw-r--r--services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/location/gnss/GnssManagerServiceTest.java22
-rw-r--r--services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java207
-rw-r--r--services/tests/servicestests/src/com/android/server/people/data/MockScheduledExecutorService.java238
-rw-r--r--services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java32
-rw-r--r--services/tests/servicestests/src/com/android/server/power/PreRebootLoggerTest.java97
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java85
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java25
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java16
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java100
-rw-r--r--telecomm/java/android/telecom/Call.java1
-rw-r--r--telecomm/java/android/telecom/Conference.java6
-rw-r--r--telecomm/java/android/telecom/Connection.java2
-rw-r--r--telecomm/java/android/telecom/TelecomManager.java3
-rw-r--r--telephony/common/com/android/internal/telephony/CarrierAppUtils.java61
-rwxr-xr-xtelephony/java/android/telephony/CarrierConfigManager.java1
-rw-r--r--tests/BootImageProfileTest/OWNERS4
-rw-r--r--tests/PlatformCompatGating/Android.bp1
-rw-r--r--tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java129
-rw-r--r--tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java134
-rw-r--r--tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java49
-rw-r--r--tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java38
-rw-r--r--tests/RollbackTest/TEST_MAPPING3
-rw-r--r--wifi/java/android/net/wifi/IWifiManager.aidl4
-rw-r--r--wifi/java/android/net/wifi/WifiConfiguration.java2
-rw-r--r--wifi/java/android/net/wifi/WifiManager.java46
-rw-r--r--wifi/java/android/net/wifi/wificond/NativeScanResult.java134
-rw-r--r--wifi/java/android/net/wifi/wificond/NativeWifiClient.java31
-rw-r--r--wifi/java/android/net/wifi/wificond/WifiCondManager.java19
-rw-r--r--wifi/tests/src/android/net/wifi/WifiManagerTest.java10
-rw-r--r--wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java40
273 files changed, 7186 insertions, 2456 deletions
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 9b9311f6c3c2..50d23ad2d657 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -232,6 +232,9 @@ droidstubs {
java_defaults {
name: "framework-stubs-default",
+ libs: [ "stub-annotations" ],
+ static_libs: [ "private-stub-annotations-jar" ],
+ sdk_version: "core_current",
errorprone: {
javacflags: [
"-XepDisableAllChecks",
@@ -247,62 +250,26 @@ java_defaults {
java_library_static {
name: "android_stubs_current",
- srcs: [
- ":api-stubs-docs",
- ],
- libs: [
- "stub-annotations",
- ],
- static_libs: [
- "private-stub-annotations-jar",
- ],
+ srcs: [ ":api-stubs-docs" ],
defaults: ["framework-stubs-default"],
- sdk_version: "core_current",
}
java_library_static {
name: "android_system_stubs_current",
- srcs: [
- ":system-api-stubs-docs",
- ],
- libs: [
- "stub-annotations",
- ],
- static_libs: [
- "private-stub-annotations-jar",
- ],
+ srcs: [ ":system-api-stubs-docs" ],
defaults: ["framework-stubs-default"],
- sdk_version: "core_current",
}
java_library_static {
name: "android_test_stubs_current",
- srcs: [
- ":test-api-stubs-docs",
- ],
- libs: [
- "stub-annotations",
- ],
- static_libs: [
- "private-stub-annotations-jar",
- ],
+ srcs: [ ":test-api-stubs-docs" ],
defaults: ["framework-stubs-default"],
- sdk_version: "core_current",
}
java_library_static {
name: "android_module_lib_stubs_current",
- srcs: [
- ":module-lib-api-stubs-docs",
- ],
- libs: [
- "stub-annotations",
- ],
- static_libs: [
- "private-stub-annotations-jar",
- ],
+ srcs: [ ":module-lib-api-stubs-docs" ],
defaults: ["framework-stubs-default"],
- sdk_version: "core_current",
}
/////////////////////////////////////////////////////////////////////
diff --git a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java
index 8cea645f4e71..821305e18240 100644
--- a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java
+++ b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java
@@ -142,6 +142,9 @@ public class BlobStoreManager {
/** @hide */
public static final int COMMIT_RESULT_ERROR = 1;
+ /** @hide */
+ public static final int INVALID_RES_ID = -1;
+
private final Context mContext;
private final IBlobStoreManager mService;
@@ -285,11 +288,65 @@ public class BlobStoreManager {
* caller is trying to acquire too many leases.
*
* @see {@link #acquireLease(BlobHandle, int)}
+ * @see {@link #acquireLease(BlobHandle, CharSequence)}
*/
public void acquireLease(@NonNull BlobHandle blobHandle, @IdRes int descriptionResId,
@CurrentTimeMillisLong long leaseExpiryTimeMillis) throws IOException {
try {
- mService.acquireLease(blobHandle, descriptionResId, leaseExpiryTimeMillis,
+ mService.acquireLease(blobHandle, descriptionResId, null, leaseExpiryTimeMillis,
+ mContext.getOpPackageName());
+ } catch (ParcelableException e) {
+ e.maybeRethrow(IOException.class);
+ throw new RuntimeException(e);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Acquire a lease to the blob represented by {@code blobHandle}. This lease indicates to the
+ * system that the caller wants the blob to be kept around.
+ *
+ * <p> This is variant of {@link #acquireLease(BlobHandle, int, long)} taking a
+ * {@link CharSequence} for {@code description}. It is highly recommended that callers only
+ * use this when a valid resource ID for {@code description} could not be provided. Otherwise,
+ * apps should prefer using {@link #acquireLease(BlobHandle, int)} which will allow
+ * {@code description} to be localized.
+ *
+ * <p> Any active leases will be automatically released when the blob's expiry time
+ * ({@link BlobHandle#getExpiryTimeMillis()}) is elapsed.
+ *
+ * <p> This lease information is persisted and calling this more than once will result in
+ * latest lease overriding any previous lease.
+ *
+ * @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to
+ * acquire a lease for.
+ * @param description a short description string that can be surfaced
+ * to the user explaining what the blob is used for.
+ * @param leaseExpiryTimeMillis the time in milliseconds after which the lease can be
+ * automatically released, in {@link System#currentTimeMillis()}
+ * timebase. If its value is {@code 0}, then the behavior of this
+ * API is identical to {@link #acquireLease(BlobHandle, int)}
+ * where clients have to explicitly call
+ * {@link #releaseLease(BlobHandle)} when they don't
+ * need the blob anymore.
+ *
+ * @throws IOException when there is an I/O error while acquiring a lease to the blob.
+ * @throws SecurityException when the blob represented by the {@code blobHandle} does not
+ * exist or the caller does not have access to it.
+ * @throws IllegalArgumentException when {@code blobHandle} is invalid or
+ * if the {@code leaseExpiryTimeMillis} is greater than the
+ * {@link BlobHandle#getExpiryTimeMillis()}.
+ * @throws IllegalStateException when a lease could not be acquired, such as when the
+ * caller is trying to acquire too many leases.
+ *
+ * @see {@link #acquireLease(BlobHandle, int, long)}
+ * @see {@link #acquireLease(BlobHandle, CharSequence)}
+ */
+ public void acquireLease(@NonNull BlobHandle blobHandle, @NonNull CharSequence description,
+ @CurrentTimeMillisLong long leaseExpiryTimeMillis) throws IOException {
+ try {
+ mService.acquireLease(blobHandle, INVALID_RES_ID, description, leaseExpiryTimeMillis,
mContext.getOpPackageName());
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
@@ -327,6 +384,7 @@ public class BlobStoreManager {
* caller is trying to acquire too many leases.
*
* @see {@link #acquireLease(BlobHandle, int, long)}
+ * @see {@link #acquireLease(BlobHandle, CharSequence, long)}
*/
public void acquireLease(@NonNull BlobHandle blobHandle, @IdRes int descriptionResId)
throws IOException {
@@ -334,6 +392,47 @@ public class BlobStoreManager {
}
/**
+ * Acquire a lease to the blob represented by {@code blobHandle}. This lease indicates to the
+ * system that the caller wants the blob to be kept around.
+ *
+ * <p> This is variant of {@link #acquireLease(BlobHandle, int)} taking a {@link CharSequence}
+ * for {@code description}. It is highly recommended that callers only use this when a valid
+ * resource ID for {@code description} could not be provided. Otherwise, apps should prefer
+ * using {@link #acquireLease(BlobHandle, int)} which will allow {@code description} to be
+ * localized.
+ *
+ * <p> This is similar to {@link #acquireLease(BlobHandle, CharSequence, long)} except clients
+ * don't have to specify the lease expiry time upfront using this API and need to explicitly
+ * release the lease using {@link #releaseLease(BlobHandle)} when they no longer like to keep
+ * a blob around.
+ *
+ * <p> Any active leases will be automatically released when the blob's expiry time
+ * ({@link BlobHandle#getExpiryTimeMillis()}) is elapsed.
+ *
+ * <p> This lease information is persisted and calling this more than once will result in
+ * latest lease overriding any previous lease.
+ *
+ * @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to
+ * acquire a lease for.
+ * @param description a short description string that can be surfaced
+ * to the user explaining what the blob is used for.
+ *
+ * @throws IOException when there is an I/O error while acquiring a lease to the blob.
+ * @throws SecurityException when the blob represented by the {@code blobHandle} does not
+ * exist or the caller does not have access to it.
+ * @throws IllegalArgumentException when {@code blobHandle} is invalid.
+ * @throws IllegalStateException when a lease could not be acquired, such as when the
+ * caller is trying to acquire too many leases.
+ *
+ * @see {@link #acquireLease(BlobHandle, int)}
+ * @see {@link #acquireLease(BlobHandle, CharSequence, long)}
+ */
+ public void acquireLease(@NonNull BlobHandle blobHandle, @NonNull CharSequence description)
+ throws IOException {
+ acquireLease(blobHandle, description, 0);
+ }
+
+ /**
* Release all active leases to the blob represented by {@code blobHandle} which are
* currently held by the caller.
*
diff --git a/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl b/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl
index e2128b421746..a85a25c9c4ad 100644
--- a/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl
+++ b/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl
@@ -26,8 +26,8 @@ interface IBlobStoreManager {
ParcelFileDescriptor openBlob(in BlobHandle handle, in String packageName);
void deleteSession(long sessionId, in String packageName);
- void acquireLease(in BlobHandle handle, int descriptionResId, long leaseTimeout,
- in String packageName);
+ void acquireLease(in BlobHandle handle, int descriptionResId, in CharSequence description,
+ long leaseTimeoutMillis, in String packageName);
void releaseLease(in BlobHandle handle, in String packageName);
void waitForIdle(in RemoteCallback callback);
diff --git a/apex/blobstore/framework/java/android/app/blob/XmlTags.java b/apex/blobstore/framework/java/android/app/blob/XmlTags.java
index 803c9a40e5ea..9834d7477838 100644
--- a/apex/blobstore/framework/java/android/app/blob/XmlTags.java
+++ b/apex/blobstore/framework/java/android/app/blob/XmlTags.java
@@ -52,4 +52,5 @@ public final class XmlTags {
// For leasee
public static final String TAG_LEASEE = "l";
public static final String ATTR_DESCRIPTION_RES_ID = "rid";
+ public static final String ATTR_DESCRIPTION = "d";
}
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
index c12e0ec8aec9..c7d803c88410 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
@@ -15,6 +15,7 @@
*/
package com.android.server.blob;
+import static android.app.blob.XmlTags.ATTR_DESCRIPTION;
import static android.app.blob.XmlTags.ATTR_DESCRIPTION_RES_ID;
import static android.app.blob.XmlTags.ATTR_EXPIRY_TIME;
import static android.app.blob.XmlTags.ATTR_ID;
@@ -28,12 +29,14 @@ import static android.app.blob.XmlTags.TAG_LEASEE;
import static android.system.OsConstants.O_RDONLY;
import static com.android.server.blob.BlobStoreConfig.TAG;
+import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_STRING_DESC;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.blob.BlobHandle;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.res.ResourceId;
import android.content.res.Resources;
import android.os.ParcelFileDescriptor;
import android.os.RevocableFileDescriptor;
@@ -141,11 +144,11 @@ class BlobMetadata {
}
}
- void addLeasee(String callingPackage, int callingUid,
- int descriptionResId, long leaseExpiryTimeMillis) {
+ void addLeasee(String callingPackage, int callingUid, int descriptionResId,
+ CharSequence description, long leaseExpiryTimeMillis) {
synchronized (mMetadataLock) {
mLeasees.add(new Leasee(callingPackage, callingUid,
- descriptionResId, leaseExpiryTimeMillis));
+ descriptionResId, description, leaseExpiryTimeMillis));
}
}
@@ -308,7 +311,7 @@ class BlobMetadata {
}
@Nullable
- static BlobMetadata createFromXml(Context context, XmlPullParser in)
+ static BlobMetadata createFromXml(XmlPullParser in, int version, Context context)
throws XmlPullParserException, IOException {
final long blobId = XmlUtils.readLongAttribute(in, ATTR_ID);
final int userId = XmlUtils.readIntAttribute(in, ATTR_USER_ID);
@@ -321,12 +324,12 @@ class BlobMetadata {
if (TAG_BLOB_HANDLE.equals(in.getName())) {
blobHandle = BlobHandle.createFromXml(in);
} else if (TAG_COMMITTER.equals(in.getName())) {
- final Committer committer = Committer.createFromXml(in);
+ final Committer committer = Committer.createFromXml(in, version);
if (committer != null) {
committers.add(committer);
}
} else if (TAG_LEASEE.equals(in.getName())) {
- leasees.add(Leasee.createFromXml(in));
+ leasees.add(Leasee.createFromXml(in, version));
}
}
@@ -366,7 +369,7 @@ class BlobMetadata {
}
@Nullable
- static Committer createFromXml(@NonNull XmlPullParser in)
+ static Committer createFromXml(@NonNull XmlPullParser in, int version)
throws XmlPullParserException, IOException {
final String packageName = XmlUtils.readStringAttribute(in, ATTR_PACKAGE);
final int uid = XmlUtils.readIntAttribute(in, ATTR_UID);
@@ -388,12 +391,15 @@ class BlobMetadata {
static final class Leasee extends Accessor {
public final int descriptionResId;
+ public final CharSequence description;
public final long expiryTimeMillis;
- Leasee(String packageName, int uid, int descriptionResId, long expiryTimeMillis) {
+ Leasee(String packageName, int uid, int descriptionResId, CharSequence description,
+ long expiryTimeMillis) {
super(packageName, uid);
this.descriptionResId = descriptionResId;
this.expiryTimeMillis = expiryTimeMillis;
+ this.description = description;
}
boolean isStillValid() {
@@ -401,18 +407,27 @@ class BlobMetadata {
}
void dump(Context context, IndentingPrintWriter fout) {
+ fout.println("desc: " + getDescriptionToDump(context));
+ fout.println("expiryMs: " + expiryTimeMillis);
+ }
+
+ private String getDescriptionToDump(Context context) {
String desc = null;
- try {
- final Resources leaseeRes = context.getPackageManager()
- .getResourcesForApplicationAsUser(packageName, UserHandle.getUserId(uid));
- desc = leaseeRes.getString(descriptionResId);
- } catch (PackageManager.NameNotFoundException e) {
- Slog.d(TAG, "Unknown package in user " + UserHandle.getUserId(uid) + ": "
- + packageName, e);
- desc = "<none>";
+ if (ResourceId.isValid(descriptionResId)) {
+ try {
+ final Resources leaseeRes = context.getPackageManager()
+ .getResourcesForApplicationAsUser(
+ packageName, UserHandle.getUserId(uid));
+ desc = leaseeRes.getString(descriptionResId);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.d(TAG, "Unknown package in user " + UserHandle.getUserId(uid) + ": "
+ + packageName, e);
+ desc = "<none>";
+ }
+ } else {
+ desc = description.toString();
}
- fout.println("desc: " + desc);
- fout.println("expiryMs: " + expiryTimeMillis);
+ return desc;
}
void writeToXml(@NonNull XmlSerializer out) throws IOException {
@@ -420,16 +435,23 @@ class BlobMetadata {
XmlUtils.writeIntAttribute(out, ATTR_UID, uid);
XmlUtils.writeIntAttribute(out, ATTR_DESCRIPTION_RES_ID, descriptionResId);
XmlUtils.writeLongAttribute(out, ATTR_EXPIRY_TIME, expiryTimeMillis);
+ XmlUtils.writeStringAttribute(out, ATTR_DESCRIPTION, description);
}
@NonNull
- static Leasee createFromXml(@NonNull XmlPullParser in) throws IOException {
+ static Leasee createFromXml(@NonNull XmlPullParser in, int version) throws IOException {
final String packageName = XmlUtils.readStringAttribute(in, ATTR_PACKAGE);
final int uid = XmlUtils.readIntAttribute(in, ATTR_UID);
final int descriptionResId = XmlUtils.readIntAttribute(in, ATTR_DESCRIPTION_RES_ID);
final long expiryTimeMillis = XmlUtils.readLongAttribute(in, ATTR_EXPIRY_TIME);
+ final CharSequence description;
+ if (version >= XML_VERSION_ADD_STRING_DESC) {
+ description = XmlUtils.readStringAttribute(in, ATTR_DESCRIPTION);
+ } else {
+ description = null;
+ }
- return new Leasee(packageName, uid, descriptionResId, expiryTimeMillis);
+ return new Leasee(packageName, uid, descriptionResId, description, expiryTimeMillis);
}
}
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
index ba2e559afdab..bcc1610435d9 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
@@ -28,7 +28,12 @@ class BlobStoreConfig {
public static final String TAG = "BlobStore";
public static final boolean LOGV = Log.isLoggable(TAG, Log.VERBOSE);
- public static final int CURRENT_XML_VERSION = 1;
+ // Initial version.
+ public static final int XML_VERSION_INIT = 1;
+ // Added a string variant of lease description.
+ public static final int XML_VERSION_ADD_STRING_DESC = 2;
+
+ public static final int XML_VERSION_CURRENT = XML_VERSION_ADD_STRING_DESC;
private static final String ROOT_DIR_NAME = "blobstore";
private static final String BLOBS_DIR_NAME = "blobs";
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
index 0ba34cab6560..1efdbda97fe5 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
@@ -27,10 +27,10 @@ import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
import static android.os.UserHandle.USER_NULL;
-import static com.android.server.blob.BlobStoreConfig.CURRENT_XML_VERSION;
import static com.android.server.blob.BlobStoreConfig.LOGV;
import static com.android.server.blob.BlobStoreConfig.SESSION_EXPIRY_TIMEOUT_MILLIS;
import static com.android.server.blob.BlobStoreConfig.TAG;
+import static com.android.server.blob.BlobStoreConfig.XML_VERSION_CURRENT;
import static com.android.server.blob.BlobStoreSession.STATE_ABANDONED;
import static com.android.server.blob.BlobStoreSession.STATE_COMMITTED;
import static com.android.server.blob.BlobStoreSession.STATE_VERIFIED_INVALID;
@@ -324,7 +324,8 @@ public class BlobStoreManagerService extends SystemService {
}
private void acquireLeaseInternal(BlobHandle blobHandle, int descriptionResId,
- long leaseExpiryTimeMillis, int callingUid, String callingPackage) {
+ CharSequence description, long leaseExpiryTimeMillis,
+ int callingUid, String callingPackage) {
synchronized (mBlobsLock) {
final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid))
.get(blobHandle);
@@ -338,7 +339,7 @@ public class BlobStoreManagerService extends SystemService {
"Lease expiry cannot be later than blobs expiry time");
}
blobMetadata.addLeasee(callingPackage, callingUid,
- descriptionResId, leaseExpiryTimeMillis);
+ descriptionResId, description, leaseExpiryTimeMillis);
if (LOGV) {
Slog.v(TAG, "Acquired lease on " + blobHandle
+ "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
@@ -450,7 +451,7 @@ public class BlobStoreManagerService extends SystemService {
out.setOutput(fos, StandardCharsets.UTF_8.name());
out.startDocument(null, true);
out.startTag(null, TAG_SESSIONS);
- XmlUtils.writeIntAttribute(out, ATTR_VERSION, CURRENT_XML_VERSION);
+ XmlUtils.writeIntAttribute(out, ATTR_VERSION, XML_VERSION_CURRENT);
for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) {
final LongSparseArray<BlobStoreSession> userSessions =
@@ -491,6 +492,7 @@ public class BlobStoreManagerService extends SystemService {
final XmlPullParser in = Xml.newPullParser();
in.setInput(fis, StandardCharsets.UTF_8.name());
XmlUtils.beginDocument(in, TAG_SESSIONS);
+ final int version = XmlUtils.readIntAttribute(in, ATTR_VERSION);
while (true) {
XmlUtils.nextElement(in);
if (in.getEventType() == XmlPullParser.END_DOCUMENT) {
@@ -499,7 +501,7 @@ public class BlobStoreManagerService extends SystemService {
if (TAG_SESSION.equals(in.getName())) {
final BlobStoreSession session = BlobStoreSession.createFromXml(
- in, mContext, mSessionStateChangeListener);
+ in, version, mContext, mSessionStateChangeListener);
if (session == null) {
continue;
}
@@ -539,7 +541,7 @@ public class BlobStoreManagerService extends SystemService {
out.setOutput(fos, StandardCharsets.UTF_8.name());
out.startDocument(null, true);
out.startTag(null, TAG_BLOBS);
- XmlUtils.writeIntAttribute(out, ATTR_VERSION, CURRENT_XML_VERSION);
+ XmlUtils.writeIntAttribute(out, ATTR_VERSION, XML_VERSION_CURRENT);
for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
@@ -579,6 +581,7 @@ public class BlobStoreManagerService extends SystemService {
final XmlPullParser in = Xml.newPullParser();
in.setInput(fis, StandardCharsets.UTF_8.name());
XmlUtils.beginDocument(in, TAG_BLOBS);
+ final int version = XmlUtils.readIntAttribute(in, ATTR_VERSION);
while (true) {
XmlUtils.nextElement(in);
if (in.getEventType() == XmlPullParser.END_DOCUMENT) {
@@ -586,7 +589,8 @@ public class BlobStoreManagerService extends SystemService {
}
if (TAG_BLOB.equals(in.getName())) {
- final BlobMetadata blobMetadata = BlobMetadata.createFromXml(mContext, in);
+ final BlobMetadata blobMetadata = BlobMetadata.createFromXml(
+ in, version, mContext);
final SparseArray<String> userPackages = allPackages.get(
blobMetadata.getUserId());
if (userPackages == null) {
@@ -1032,11 +1036,14 @@ public class BlobStoreManagerService extends SystemService {
@Override
public void acquireLease(@NonNull BlobHandle blobHandle, @IdRes int descriptionResId,
+ @Nullable CharSequence description,
@CurrentTimeSecondsLong long leaseExpiryTimeMillis, @NonNull String packageName) {
Objects.requireNonNull(blobHandle, "blobHandle must not be null");
blobHandle.assertIsValid();
- Preconditions.checkArgument(ResourceId.isValid(descriptionResId),
- "descriptionResId is not valid");
+ Preconditions.checkArgument(
+ ResourceId.isValid(descriptionResId) || description != null,
+ "Description must be valid; descriptionId=" + descriptionResId
+ + ", description=" + description);
Preconditions.checkArgumentNonnegative(leaseExpiryTimeMillis,
"leaseExpiryTimeMillis must not be negative");
Objects.requireNonNull(packageName, "packageName must not be null");
@@ -1044,7 +1051,7 @@ public class BlobStoreManagerService extends SystemService {
final int callingUid = Binder.getCallingUid();
verifyCallingPackage(callingUid, packageName);
- acquireLeaseInternal(blobHandle, descriptionResId, leaseExpiryTimeMillis,
+ acquireLeaseInternal(blobHandle, descriptionResId, description, leaseExpiryTimeMillis,
callingUid, packageName);
}
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
index bd35b86babd8..80b42355ef7c 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
@@ -511,7 +511,7 @@ class BlobStoreSession extends IBlobStoreSession.Stub {
}
@Nullable
- static BlobStoreSession createFromXml(@NonNull XmlPullParser in,
+ static BlobStoreSession createFromXml(@NonNull XmlPullParser in, int version,
@NonNull Context context, @NonNull SessionStateChangeListener stateChangeListener)
throws IOException, XmlPullParserException {
final int sessionId = XmlUtils.readIntAttribute(in, ATTR_ID);
diff --git a/api/current.txt b/api/current.txt
index 826d409b0a2d..475506a317bc 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -7574,7 +7574,9 @@ package android.app.blob {
public class BlobStoreManager {
method public void acquireLease(@NonNull android.app.blob.BlobHandle, @IdRes int, long) throws java.io.IOException;
+ method public void acquireLease(@NonNull android.app.blob.BlobHandle, @NonNull CharSequence, long) throws java.io.IOException;
method public void acquireLease(@NonNull android.app.blob.BlobHandle, @IdRes int) throws java.io.IOException;
+ method public void acquireLease(@NonNull android.app.blob.BlobHandle, @NonNull CharSequence) throws java.io.IOException;
method @IntRange(from=1) public long createSession(@NonNull android.app.blob.BlobHandle) throws java.io.IOException;
method public void deleteSession(@IntRange(from=1) long) throws java.io.IOException;
method @NonNull public android.os.ParcelFileDescriptor openBlob(@NonNull android.app.blob.BlobHandle) throws java.io.IOException;
@@ -12575,6 +12577,7 @@ package android.content.res {
method public int getLayoutDirection();
method @NonNull public android.os.LocaleList getLocales();
method public boolean isLayoutSizeAtLeast(int);
+ method public boolean isNightModeActive();
method public boolean isScreenHdr();
method public boolean isScreenRound();
method public boolean isScreenWideColorGamut();
@@ -17032,6 +17035,7 @@ package android.hardware.biometrics {
field public static final int BIOMETRIC_ERROR_HW_UNAVAILABLE = 1; // 0x1
field public static final int BIOMETRIC_ERROR_NONE_ENROLLED = 11; // 0xb
field public static final int BIOMETRIC_ERROR_NO_HARDWARE = 12; // 0xc
+ field public static final int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15; // 0xf
field public static final int BIOMETRIC_SUCCESS = 0; // 0x0
}
@@ -17066,6 +17070,7 @@ package android.hardware.biometrics {
field public static final int BIOMETRIC_ERROR_NO_BIOMETRICS = 11; // 0xb
field public static final int BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL = 14; // 0xe
field public static final int BIOMETRIC_ERROR_NO_SPACE = 4; // 0x4
+ field public static final int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15; // 0xf
field public static final int BIOMETRIC_ERROR_TIMEOUT = 3; // 0x3
field public static final int BIOMETRIC_ERROR_UNABLE_TO_PROCESS = 2; // 0x2
field public static final int BIOMETRIC_ERROR_USER_CANCELED = 10; // 0xa
@@ -31280,6 +31285,7 @@ package android.net.wifi {
method public int getWifiState();
method public boolean is5GHzBandSupported();
method public boolean is6GHzBandSupported();
+ method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public boolean isAutoWakeupEnabled();
method @Deprecated public boolean isDeviceToApRttSupported();
method public boolean isEasyConnectSupported();
method public boolean isEnhancedOpenSupported();
@@ -42599,6 +42605,7 @@ package android.security.keystore {
method @NonNull public String getKeystoreAlias();
method public int getPurposes();
method @NonNull public String[] getSignaturePaddings();
+ method public int getUserAuthenticationType();
method public int getUserAuthenticationValidityDurationSeconds();
method @NonNull public boolean isDigestsSpecified();
method public boolean isInvalidatedByBiometricEnrollment();
@@ -42633,9 +42640,10 @@ package android.security.keystore {
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setRandomizedEncryptionRequired(boolean);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setSignaturePaddings(java.lang.String...);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUnlockedDeviceRequired(boolean);
+ method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationParameters(@IntRange(from=0xffffffff) int, int);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationRequired(boolean);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidWhileOnBody(boolean);
- method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidityDurationSeconds(@IntRange(from=0xffffffff) int);
+ method @Deprecated @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidityDurationSeconds(@IntRange(from=0xffffffff) int);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUserConfirmationRequired(boolean);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUserPresenceRequired(boolean);
}
@@ -42652,6 +42660,7 @@ package android.security.keystore {
method public int getOrigin();
method public int getPurposes();
method @NonNull public String[] getSignaturePaddings();
+ method public int getUserAuthenticationType();
method public int getUserAuthenticationValidityDurationSeconds();
method public boolean isInsideSecureHardware();
method public boolean isInvalidatedByBiometricEnrollment();
@@ -42675,6 +42684,8 @@ package android.security.keystore {
}
public abstract class KeyProperties {
+ field public static final int AUTH_BIOMETRIC_STRONG = 2; // 0x2
+ field public static final int AUTH_DEVICE_CREDENTIAL = 1; // 0x1
field public static final String BLOCK_MODE_CBC = "CBC";
field public static final String BLOCK_MODE_CTR = "CTR";
field public static final String BLOCK_MODE_ECB = "ECB";
@@ -42721,6 +42732,7 @@ package android.security.keystore {
method @Nullable public java.util.Date getKeyValidityStart();
method public int getPurposes();
method @NonNull public String[] getSignaturePaddings();
+ method public int getUserAuthenticationType();
method public int getUserAuthenticationValidityDurationSeconds();
method public boolean isDigestsSpecified();
method public boolean isInvalidatedByBiometricEnrollment();
@@ -42746,9 +42758,10 @@ package android.security.keystore {
method @NonNull public android.security.keystore.KeyProtection.Builder setRandomizedEncryptionRequired(boolean);
method @NonNull public android.security.keystore.KeyProtection.Builder setSignaturePaddings(java.lang.String...);
method @NonNull public android.security.keystore.KeyProtection.Builder setUnlockedDeviceRequired(boolean);
+ method @NonNull public android.security.keystore.KeyProtection.Builder setUserAuthenticationParameters(@IntRange(from=0xffffffff) int, int);
method @NonNull public android.security.keystore.KeyProtection.Builder setUserAuthenticationRequired(boolean);
method @NonNull public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidWhileOnBody(boolean);
- method @NonNull public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidityDurationSeconds(@IntRange(from=0xffffffff) int);
+ method @Deprecated @NonNull public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidityDurationSeconds(@IntRange(from=0xffffffff) int);
method @NonNull public android.security.keystore.KeyProtection.Builder setUserConfirmationRequired(boolean);
method @NonNull public android.security.keystore.KeyProtection.Builder setUserPresenceRequired(boolean);
}
@@ -45426,7 +45439,6 @@ package android.telecom {
field public static final int PROPERTY_GENERIC_CONFERENCE = 2; // 0x2
field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 128; // 0x80
field public static final int PROPERTY_HIGH_DEF_AUDIO = 16; // 0x10
- field public static final int PROPERTY_IS_ADHOC_CONFERENCE = 8192; // 0x2000
field public static final int PROPERTY_IS_EXTERNAL_CALL = 64; // 0x40
field public static final int PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL = 2048; // 0x800
field public static final int PROPERTY_RTT = 1024; // 0x400
@@ -45504,7 +45516,6 @@ package android.telecom {
public abstract class Conference extends android.telecom.Conferenceable {
ctor public Conference(android.telecom.PhoneAccountHandle);
method public final boolean addConnection(android.telecom.Connection);
- method @NonNull public static android.telecom.Conference createFailedConference(@NonNull android.telecom.DisconnectCause, @NonNull android.telecom.PhoneAccountHandle);
method public final void destroy();
method public final android.telecom.CallAudioState getCallAudioState();
method public final java.util.List<android.telecom.Connection> getConferenceableConnections();
@@ -45519,8 +45530,6 @@ package android.telecom {
method public final android.telecom.StatusHints getStatusHints();
method public android.telecom.Connection.VideoProvider getVideoProvider();
method public int getVideoState();
- method public final boolean isRingbackRequested();
- method public void onAnswer(int);
method public void onCallAudioStateChanged(android.telecom.CallAudioState);
method public void onConnectionAdded(android.telecom.Connection);
method public void onDisconnect();
@@ -45529,7 +45538,6 @@ package android.telecom {
method public void onMerge(android.telecom.Connection);
method public void onMerge();
method public void onPlayDtmfTone(char);
- method public void onReject();
method public void onSeparate(android.telecom.Connection);
method public void onStopDtmfTone();
method public void onSwap();
@@ -45549,8 +45557,6 @@ package android.telecom {
method public final void setDisconnected(android.telecom.DisconnectCause);
method public final void setExtras(@Nullable android.os.Bundle);
method public final void setOnHold();
- method public final void setRingbackRequested(boolean);
- method public final void setRinging();
method public final void setStatusHints(android.telecom.StatusHints);
method public final void setVideoProvider(android.telecom.Connection, android.telecom.Connection.VideoProvider);
method public final void setVideoState(android.telecom.Connection, int);
@@ -45709,7 +45715,6 @@ package android.telecom {
field public static final int PROPERTY_ASSISTED_DIALING_USED = 512; // 0x200
field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 32; // 0x20
field public static final int PROPERTY_HIGH_DEF_AUDIO = 4; // 0x4
- field public static final int PROPERTY_IS_ADHOC_CONFERENCE = 4096; // 0x1000
field public static final int PROPERTY_IS_EXTERNAL_CALL = 16; // 0x10
field public static final int PROPERTY_IS_RTT = 256; // 0x100
field public static final int PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL = 1024; // 0x400
@@ -46125,7 +46130,6 @@ package android.telecom {
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ANSWER_PHONE_CALLS, android.Manifest.permission.MODIFY_PHONE_STATE}) public void acceptRingingCall();
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ANSWER_PHONE_CALLS, android.Manifest.permission.MODIFY_PHONE_STATE}) public void acceptRingingCall(int);
method public void addNewIncomingCall(android.telecom.PhoneAccountHandle, android.os.Bundle);
- method public void addNewIncomingConference(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.os.Bundle);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void cancelMissedCallsNotification();
method public android.content.Intent createManageBlockedNumbersIntent();
method @Deprecated @RequiresPermission(android.Manifest.permission.ANSWER_PHONE_CALLS) public boolean endCall();
@@ -46153,7 +46157,6 @@ package android.telecom {
method public void registerPhoneAccount(android.telecom.PhoneAccount);
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void showInCallScreen(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void silenceRinger();
- method @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void startConference(@NonNull java.util.List<android.net.Uri>, @NonNull android.os.Bundle);
method public void unregisterPhoneAccount(android.telecom.PhoneAccountHandle);
field public static final String ACTION_CHANGE_DEFAULT_DIALER = "android.telecom.action.CHANGE_DEFAULT_DIALER";
field public static final String ACTION_CHANGE_PHONE_ACCOUNTS = "android.telecom.action.CHANGE_PHONE_ACCOUNTS";
@@ -46680,7 +46683,6 @@ package android.telephony {
field public static final String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool";
field public static final String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool";
field public static final String KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL = "support_3gpp_call_forwarding_while_roaming_bool";
- field public static final String KEY_SUPPORT_ADHOC_CONFERENCE_CALLS_BOOL = "support_adhoc_conference_calls_bool";
field public static final String KEY_SUPPORT_CLIR_NETWORK_DEFAULT_BOOL = "support_clir_network_default_bool";
field public static final String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool";
field public static final String KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL = "support_emergency_sms_over_ims_bool";
diff --git a/api/system-current.txt b/api/system-current.txt
index 24936d5784f8..a428d13fc6d9 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1451,16 +1451,16 @@ package android.app.usage {
package android.bluetooth {
public final class BluetoothA2dp implements android.bluetooth.BluetoothProfile {
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void disableOptionalCodecs(@Nullable android.bluetooth.BluetoothDevice);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void enableOptionalCodecs(@Nullable android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void disableOptionalCodecs(@NonNull android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void enableOptionalCodecs(@NonNull android.bluetooth.BluetoothDevice);
method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.bluetooth.BluetoothDevice getActiveDevice();
- method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.bluetooth.BluetoothCodecStatus getCodecStatus(@Nullable android.bluetooth.BluetoothDevice);
+ method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.bluetooth.BluetoothCodecStatus getCodecStatus(@NonNull android.bluetooth.BluetoothDevice);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public int getOptionalCodecsEnabled(@Nullable android.bluetooth.BluetoothDevice);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void setCodecConfigPreference(@Nullable android.bluetooth.BluetoothDevice, @Nullable android.bluetooth.BluetoothCodecConfig);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public int isOptionalCodecsEnabled(@NonNull android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public int isOptionalCodecsSupported(@NonNull android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void setCodecConfigPreference(@NonNull android.bluetooth.BluetoothDevice, @NonNull android.bluetooth.BluetoothCodecConfig);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void setOptionalCodecsEnabled(@Nullable android.bluetooth.BluetoothDevice, int);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public int supportsOptionalCodecs(@Nullable android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void setOptionalCodecsEnabled(@NonNull android.bluetooth.BluetoothDevice, int);
field public static final int OPTIONAL_CODECS_NOT_SUPPORTED = 0; // 0x0
field public static final int OPTIONAL_CODECS_PREF_DISABLED = 0; // 0x0
field public static final int OPTIONAL_CODECS_PREF_ENABLED = 1; // 0x1
@@ -1471,10 +1471,10 @@ package android.bluetooth {
public final class BluetoothA2dpSink implements android.bluetooth.BluetoothProfile {
method public void finalize();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isAudioPlaying(@Nullable android.bluetooth.BluetoothDevice);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice, int);
- field @RequiresPermission(android.Manifest.permission.BLUETOOTH) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED";
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean isAudioPlaying(@Nullable android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice, int);
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED";
}
public final class BluetoothAdapter {
@@ -1647,9 +1647,9 @@ package android.bluetooth {
}
public class BluetoothPbap implements android.bluetooth.BluetoothProfile {
- method public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
- field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
}
public interface BluetoothProfile {
@@ -1922,18 +1922,22 @@ package android.content.integrity {
public abstract class IntegrityFormula {
method @NonNull public static android.content.integrity.IntegrityFormula all(@NonNull android.content.integrity.IntegrityFormula...);
method @NonNull public static android.content.integrity.IntegrityFormula any(@NonNull android.content.integrity.IntegrityFormula...);
- method @NonNull public android.content.integrity.IntegrityFormula equalTo(@NonNull String);
- method @NonNull public android.content.integrity.IntegrityFormula equalTo(boolean);
- method @NonNull public android.content.integrity.IntegrityFormula equalTo(long);
- method @NonNull public android.content.integrity.IntegrityFormula greaterThan(long);
- method @NonNull public android.content.integrity.IntegrityFormula greaterThanOrEquals(long);
method @NonNull public static android.content.integrity.IntegrityFormula not(@NonNull android.content.integrity.IntegrityFormula);
- field @NonNull public static final android.content.integrity.IntegrityFormula APP_CERTIFICATE;
- field @NonNull public static final android.content.integrity.IntegrityFormula INSTALLER_CERTIFICATE;
- field @NonNull public static final android.content.integrity.IntegrityFormula INSTALLER_NAME;
- field @NonNull public static final android.content.integrity.IntegrityFormula PACKAGE_NAME;
- field @NonNull public static final android.content.integrity.IntegrityFormula PRE_INSTALLED;
- field @NonNull public static final android.content.integrity.IntegrityFormula VERSION_CODE;
+ }
+
+ public static final class IntegrityFormula.Application {
+ method @NonNull public static android.content.integrity.IntegrityFormula certificatesContain(@NonNull String);
+ method @NonNull public static android.content.integrity.IntegrityFormula isPreInstalled();
+ method @NonNull public static android.content.integrity.IntegrityFormula packageNameEquals(@NonNull String);
+ method @NonNull public static android.content.integrity.IntegrityFormula versionCodeEquals(@NonNull long);
+ method @NonNull public static android.content.integrity.IntegrityFormula versionCodeGreaterThan(@NonNull long);
+ method @NonNull public static android.content.integrity.IntegrityFormula versionCodeGreaterThanOrEqualTo(@NonNull long);
+ }
+
+ public static final class IntegrityFormula.Installer {
+ method @NonNull public static android.content.integrity.IntegrityFormula certificatesContain(@NonNull String);
+ method @NonNull public static android.content.integrity.IntegrityFormula notAllowedByManifest();
+ method @NonNull public static android.content.integrity.IntegrityFormula packageNameEquals(@NonNull String);
}
public final class Rule implements android.os.Parcelable {
@@ -2007,18 +2011,15 @@ package android.content.pm {
}
public final class InstallationFile implements android.os.Parcelable {
- ctor public InstallationFile(@NonNull String, long, @Nullable byte[]);
+ ctor public InstallationFile(int, @NonNull String, long, @Nullable byte[], @Nullable byte[]);
method public int describeContents();
- method public int getFileType();
+ method public long getLengthBytes();
+ method public int getLocation();
method @Nullable public byte[] getMetadata();
method @NonNull public String getName();
- method public long getSize();
+ method @Nullable public byte[] getSignature();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.InstallationFile> CREATOR;
- field public static final int FILE_TYPE_APK = 0; // 0x0
- field public static final int FILE_TYPE_LIB = 1; // 0x1
- field public static final int FILE_TYPE_OBB = 2; // 0x2
- field public static final int FILE_TYPE_UNKNOWN = -1; // 0xffffffff
}
public final class InstantAppInfo implements android.os.Parcelable {
@@ -4854,6 +4855,8 @@ package android.media.tv.tuner {
public class Tuner implements java.lang.AutoCloseable {
ctor @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public Tuner(@NonNull android.content.Context, @NonNull String, int, @Nullable android.media.tv.tuner.Tuner.OnResourceLostListener);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int cancelScanning();
+ method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int cancelTuning();
method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void clearOnTuneEventListener();
method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void close();
method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int connectCiCam(int);
@@ -4874,8 +4877,6 @@ package android.media.tv.tuner {
method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int setLna(boolean);
method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void setOnTuneEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.OnTuneEventListener);
method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void shareFrontendFromTuner(@NonNull android.media.tv.tuner.Tuner);
- method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int stopScan();
- method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int stopTune();
method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int tune(@NonNull android.media.tv.tuner.frontend.FrontendSettings);
method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void updateResourcePriority(int, int);
}
@@ -7717,6 +7718,7 @@ package android.net.wifi {
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public byte[] retrieveBackupData();
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public byte[] retrieveSoftApBackupData();
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void save(@NonNull android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiManager.ActionListener);
+ method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setAutoWakeupEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.WIFI_SET_DEVICE_MOBILITY_STATE) public void setDeviceMobilityState(int);
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setMacRandomizationSettingPasspointEnabled(@NonNull String, boolean);
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setMeteredOverridePasspoint(@NonNull String, int);
@@ -8242,8 +8244,8 @@ package android.net.wifi.wificond {
public final class NativeScanResult implements android.os.Parcelable {
ctor public NativeScanResult();
method public int describeContents();
- method @NonNull public byte[] getBssid();
- method @NonNull public int getCapabilities();
+ method @Nullable public android.net.MacAddress getBssid();
+ method public int getCapabilities();
method public int getFrequencyMhz();
method @NonNull public byte[] getInformationElements();
method @NonNull public java.util.List<android.net.wifi.wificond.RadioChainInfo> getRadioChainInfos();
@@ -8252,15 +8254,31 @@ package android.net.wifi.wificond {
method public long getTsf();
method public boolean isAssociated();
method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int BSS_CAPABILITY_APSD = 2048; // 0x800
+ field public static final int BSS_CAPABILITY_CF_POLLABLE = 4; // 0x4
+ field public static final int BSS_CAPABILITY_CF_POLL_REQUEST = 8; // 0x8
+ field public static final int BSS_CAPABILITY_CHANNEL_AGILITY = 128; // 0x80
+ field public static final int BSS_CAPABILITY_DELAYED_BLOCK_ACK = 16384; // 0x4000
+ field public static final int BSS_CAPABILITY_DSSS_OFDM = 8192; // 0x2000
+ field public static final int BSS_CAPABILITY_ESS = 1; // 0x1
+ field public static final int BSS_CAPABILITY_IBSS = 2; // 0x2
+ field public static final int BSS_CAPABILITY_IMMEDIATE_BLOCK_ACK = 32768; // 0x8000
+ field public static final int BSS_CAPABILITY_PBCC = 64; // 0x40
+ field public static final int BSS_CAPABILITY_PRIVACY = 16; // 0x10
+ field public static final int BSS_CAPABILITY_QOS = 512; // 0x200
+ field public static final int BSS_CAPABILITY_RADIO_MANAGEMENT = 4096; // 0x1000
+ field public static final int BSS_CAPABILITY_SHORT_PREAMBLE = 32; // 0x20
+ field public static final int BSS_CAPABILITY_SHORT_SLOT_TIME = 1024; // 0x400
+ field public static final int BSS_CAPABILITY_SPECTRUM_MANAGEMENT = 256; // 0x100
field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.wificond.NativeScanResult> CREATOR;
}
public final class NativeWifiClient implements android.os.Parcelable {
- ctor public NativeWifiClient(@NonNull byte[]);
+ ctor public NativeWifiClient(@Nullable android.net.MacAddress);
method public int describeContents();
+ method @Nullable public android.net.MacAddress getMacAddress();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.wificond.NativeWifiClient> CREATOR;
- field @NonNull public final byte[] macAddress;
}
public final class PnoNetwork implements android.os.Parcelable {
@@ -8305,7 +8323,7 @@ package android.net.wifi.wificond {
public class WifiCondManager {
method public void abortScan(@NonNull String);
method public void enableVerboseLogging(boolean);
- method @NonNull public java.util.List<java.lang.Integer> getChannelsMhzForBand(int);
+ method @NonNull public int[] getChannelsMhzForBand(int);
method @Nullable public android.net.wifi.wificond.DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String);
method @NonNull public java.util.List<android.net.wifi.wificond.NativeScanResult> getScanResults(@NonNull String, int);
method @Nullable public android.net.wifi.wificond.WifiCondManager.TxPacketCounters getTxPacketCounters(@NonNull String);
@@ -9680,7 +9698,6 @@ package android.provider {
field public static final String EUICC_UNSUPPORTED_COUNTRIES = "euicc_unsupported_countries";
field public static final String INSTALL_CARRIER_APP_NOTIFICATION_PERSISTENT = "install_carrier_app_notification_persistent";
field public static final String INSTALL_CARRIER_APP_NOTIFICATION_SLEEP_MILLIS = "install_carrier_app_notification_sleep_millis";
- field public static final String NETWORK_RECOMMENDATIONS_ENABLED = "network_recommendations_enabled";
field public static final String OTA_DISABLE_AUTOMATIC_UPDATE = "ota_disable_automatic_update";
field public static final String REQUIRE_PASSWORD_TO_DECRYPT = "require_password_to_decrypt";
field public static final String SOFT_AP_TIMEOUT_ENABLED = "soft_ap_timeout_enabled";
@@ -9695,7 +9712,7 @@ package android.provider {
field public static final String WIFI_SCAN_THROTTLE_ENABLED = "wifi_scan_throttle_enabled";
field public static final String WIFI_SCORE_PARAMS = "wifi_score_params";
field public static final String WIFI_VERBOSE_LOGGING_ENABLED = "wifi_verbose_logging_enabled";
- field public static final String WIFI_WAKEUP_ENABLED = "wifi_wakeup_enabled";
+ field @Deprecated public static final String WIFI_WAKEUP_ENABLED = "wifi_wakeup_enabled";
}
public static final class Settings.Secure extends android.provider.Settings.NameValueTable {
@@ -10317,7 +10334,7 @@ package android.service.dataloader {
public abstract class DataLoaderService extends android.app.Service {
ctor public DataLoaderService();
- method @Nullable public android.service.dataloader.DataLoaderService.DataLoader onCreateDataLoader();
+ method @Nullable public android.service.dataloader.DataLoaderService.DataLoader onCreateDataLoader(@NonNull android.content.pm.DataLoaderParams);
}
public static interface DataLoaderService.DataLoader {
diff --git a/api/test-current.txt b/api/test-current.txt
index e352cb65156e..78a0b0a2046c 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -812,18 +812,22 @@ package android.content.integrity {
public abstract class IntegrityFormula {
method @NonNull public static android.content.integrity.IntegrityFormula all(@NonNull android.content.integrity.IntegrityFormula...);
method @NonNull public static android.content.integrity.IntegrityFormula any(@NonNull android.content.integrity.IntegrityFormula...);
- method @NonNull public android.content.integrity.IntegrityFormula equalTo(@NonNull String);
- method @NonNull public android.content.integrity.IntegrityFormula equalTo(boolean);
- method @NonNull public android.content.integrity.IntegrityFormula equalTo(long);
- method @NonNull public android.content.integrity.IntegrityFormula greaterThan(long);
- method @NonNull public android.content.integrity.IntegrityFormula greaterThanOrEquals(long);
method @NonNull public static android.content.integrity.IntegrityFormula not(@NonNull android.content.integrity.IntegrityFormula);
- field @NonNull public static final android.content.integrity.IntegrityFormula APP_CERTIFICATE;
- field @NonNull public static final android.content.integrity.IntegrityFormula INSTALLER_CERTIFICATE;
- field @NonNull public static final android.content.integrity.IntegrityFormula INSTALLER_NAME;
- field @NonNull public static final android.content.integrity.IntegrityFormula PACKAGE_NAME;
- field @NonNull public static final android.content.integrity.IntegrityFormula PRE_INSTALLED;
- field @NonNull public static final android.content.integrity.IntegrityFormula VERSION_CODE;
+ }
+
+ public static final class IntegrityFormula.Application {
+ method @NonNull public static android.content.integrity.IntegrityFormula certificatesContain(@NonNull String);
+ method @NonNull public static android.content.integrity.IntegrityFormula isPreInstalled();
+ method @NonNull public static android.content.integrity.IntegrityFormula packageNameEquals(@NonNull String);
+ method @NonNull public static android.content.integrity.IntegrityFormula versionCodeEquals(@NonNull long);
+ method @NonNull public static android.content.integrity.IntegrityFormula versionCodeGreaterThan(@NonNull long);
+ method @NonNull public static android.content.integrity.IntegrityFormula versionCodeGreaterThanOrEqualTo(@NonNull long);
+ }
+
+ public static final class IntegrityFormula.Installer {
+ method @NonNull public static android.content.integrity.IntegrityFormula certificatesContain(@NonNull String);
+ method @NonNull public static android.content.integrity.IntegrityFormula notAllowedByManifest();
+ method @NonNull public static android.content.integrity.IntegrityFormula packageNameEquals(@NonNull String);
}
public final class Rule implements android.os.Parcelable {
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 89b1798587f0..23f9f22c94f2 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -7960,6 +7960,10 @@ message SurfaceflingerStatsLayerInfo {
// presentation, until the buffer was ready to be presented.
optional FrameTimingHistogram post_to_acquire = 9
[(android.os.statsd.log_mode) = MODE_BYTES];
+ // Frames missed latch because the acquire fence didn't fire
+ optional int64 late_acquire_frames = 10;
+ // Frames latched early because the desired present time was bad
+ optional int64 bad_desired_present_frames = 11;
}
/**
diff --git a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
index d79740b49b3d..9912d2b1cc8b 100644
--- a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
@@ -76,6 +76,16 @@ public final class AccessibilityShortcutInfo {
private final int mDescriptionResId;
/**
+ * Resource id of the animated image of the accessibility shortcut target.
+ */
+ private final int mAnimatedImageRes;
+
+ /**
+ * Resource id of the html description of the accessibility shortcut target.
+ */
+ private final int mHtmlDescriptionRes;
+
+ /**
* Creates a new instance.
*
* @param context Context for accessing resources.
@@ -119,6 +129,14 @@ public final class AccessibilityShortcutInfo {
// Gets summary
mSummaryResId = asAttributes.getResourceId(
com.android.internal.R.styleable.AccessibilityShortcutTarget_summary, 0);
+ // Gets animated image
+ mAnimatedImageRes = asAttributes.getResourceId(
+ com.android.internal.R.styleable
+ .AccessibilityShortcutTarget_animatedImageDrawable, 0);
+ // Gets html description
+ mHtmlDescriptionRes = asAttributes.getResourceId(
+ com.android.internal.R.styleable.AccessibilityShortcutTarget_htmlDescription,
+ 0);
asAttributes.recycle();
if (mDescriptionResId == 0 || mSummaryResId == 0) {
@@ -172,6 +190,25 @@ public final class AccessibilityShortcutInfo {
}
/**
+ * The animated image resource id of the accessibility shortcut target.
+ *
+ * @return The animated image resource id.
+ */
+ public int getAnimatedImageRes() {
+ return mAnimatedImageRes;
+ }
+
+ /**
+ * The localized html description of the accessibility shortcut target.
+ *
+ * @return The localized html description.
+ */
+ @Nullable
+ public String loadHtmlDescription(@NonNull PackageManager packageManager) {
+ return loadResourceString(packageManager, mActivityInfo, mHtmlDescriptionRes);
+ }
+
+ /**
* Gets string resource by the given activity and resource id.
*/
@Nullable
diff --git a/core/java/android/app/ITaskOrganizerController.aidl b/core/java/android/app/ITaskOrganizerController.aidl
index 5d5956e4dca4..9d6c3d64aaf2 100644
--- a/core/java/android/app/ITaskOrganizerController.aidl
+++ b/core/java/android/app/ITaskOrganizerController.aidl
@@ -52,7 +52,11 @@ interface ITaskOrganizerController {
boolean deleteRootTask(IWindowContainer task);
/** Gets direct child tasks (ordered from top-to-bottom) */
- List<ActivityManager.RunningTaskInfo> getChildTasks(in IWindowContainer parent);
+ List<ActivityManager.RunningTaskInfo> getChildTasks(in IWindowContainer parent,
+ in int[] activityTypes);
+
+ /** Gets all root tasks on a display (ordered from top-to-bottom) */
+ List<ActivityManager.RunningTaskInfo> getRootTasks(int displayId, in int[] activityTypes);
/** Get the root task which contains the current ime target */
IWindowContainer getImeTarget(int display);
diff --git a/core/java/android/app/PictureInPictureParams.java b/core/java/android/app/PictureInPictureParams.java
index bfd966c575a9..67d94dec88c5 100644
--- a/core/java/android/app/PictureInPictureParams.java
+++ b/core/java/android/app/PictureInPictureParams.java
@@ -247,6 +247,14 @@ public final class PictureInPictureParams implements Parcelable {
return mSourceRectHint != null && !mSourceRectHint.isEmpty();
}
+ /**
+ * @return True if no parameters are set
+ * @hide
+ */
+ public boolean empty() {
+ return !hasSourceBoundsHint() && !hasSetActions() && !hasSetAspectRatio();
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 662ca6eb2c19..f7d712dbd4fb 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -149,6 +149,13 @@ public class TaskInfo {
public IWindowContainer token;
/**
+ * The PictureInPictureParams for the Task, if set.
+ * @hide
+ */
+ @Nullable
+ public PictureInPictureParams pictureInPictureParams;
+
+ /**
* The activity type of the top activity in this task.
* @hide
*/
@@ -209,6 +216,9 @@ public class TaskInfo {
configuration.readFromParcel(source);
token = IWindowContainer.Stub.asInterface(source.readStrongBinder());
topActivityType = source.readInt();
+ pictureInPictureParams = source.readInt() != 0
+ ? PictureInPictureParams.CREATOR.createFromParcel(source)
+ : null;
}
/**
@@ -246,6 +256,12 @@ public class TaskInfo {
configuration.writeToParcel(dest, flags);
dest.writeStrongInterface(token);
dest.writeInt(topActivityType);
+ if (pictureInPictureParams == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(1);
+ pictureInPictureParams.writeToParcel(dest, flags);
+ }
}
@Override
@@ -261,6 +277,7 @@ public class TaskInfo {
+ " supportsSplitScreenMultiWindow=" + supportsSplitScreenMultiWindow
+ " resizeMode=" + resizeMode
+ " token=" + token
- + " topActivityType=" + topActivityType;
+ + " topActivityType=" + topActivityType
+ + " pictureInPictureParams=" + pictureInPictureParams;
}
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 9cec514ec592..dc15b51a442c 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -8951,7 +8951,8 @@ public class DevicePolicyManager {
*
* <strong>Note: Starting from Android R, apps should no longer call this method with the
* setting {@link android.provider.Settings.Secure#LOCATION_MODE}, which is deprecated. Instead,
- * device owners should call {@link #setLocationEnabled(ComponentName, boolean)}.
+ * device owners should call {@link #setLocationEnabled(ComponentName, boolean)}. This will be
+ * enforced for all apps targeting Android R or above.
* </strong>
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
@@ -8961,6 +8962,7 @@ public class DevicePolicyManager {
*/
public void setSecureSetting(@NonNull ComponentName admin, String setting, String value) {
throwIfParentInstance("setSecureSetting");
+
if (mService != null) {
try {
mService.setSecureSetting(admin, setting, value);
diff --git a/core/java/android/app/compat/TEST_MAPPING b/core/java/android/app/compat/TEST_MAPPING
new file mode 100644
index 000000000000..c047df514e8d
--- /dev/null
+++ b/core/java/android/app/compat/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "imports": [
+ {
+ "path": "frameworks/base/services/core/java/com/android/services/compat"
+ }
+ ]
+} \ No newline at end of file
diff --git a/core/java/android/app/prediction/AppPredictionSessionId.java b/core/java/android/app/prediction/AppPredictionSessionId.java
index e5e06f859ac6..876bafdfb7d1 100644
--- a/core/java/android/app/prediction/AppPredictionSessionId.java
+++ b/core/java/android/app/prediction/AppPredictionSessionId.java
@@ -22,6 +22,8 @@ import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.Objects;
+
/**
* The id for an app prediction session. See {@link AppPredictor}.
*
@@ -32,18 +34,28 @@ import android.os.Parcelable;
public final class AppPredictionSessionId implements Parcelable {
private final String mId;
+ private final int mUserId;
/**
* Creates a new id for a prediction session.
*
* @hide
*/
- public AppPredictionSessionId(@NonNull String id) {
+ public AppPredictionSessionId(@NonNull final String id, final int userId) {
mId = id;
+ mUserId = userId;
}
private AppPredictionSessionId(Parcel p) {
mId = p.readString();
+ mUserId = p.readInt();
+ }
+
+ /**
+ * @hide
+ */
+ public int getUserId() {
+ return mUserId;
}
@Override
@@ -51,17 +63,17 @@ public final class AppPredictionSessionId implements Parcelable {
if (!getClass().equals(o != null ? o.getClass() : null)) return false;
AppPredictionSessionId other = (AppPredictionSessionId) o;
- return mId.equals(other.mId);
+ return mId.equals(other.mId) && mUserId == other.mUserId;
}
@Override
public @NonNull String toString() {
- return mId;
+ return mId + "," + mUserId;
}
@Override
public int hashCode() {
- return mId.hashCode();
+ return Objects.hash(mId, mUserId);
}
@Override
@@ -72,6 +84,7 @@ public final class AppPredictionSessionId implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mId);
+ dest.writeInt(mUserId);
}
public static final @android.annotation.NonNull Parcelable.Creator<AppPredictionSessionId> CREATOR =
diff --git a/core/java/android/app/prediction/AppPredictor.java b/core/java/android/app/prediction/AppPredictor.java
index cd635d635ce1..f0eedf3d18f2 100644
--- a/core/java/android/app/prediction/AppPredictor.java
+++ b/core/java/android/app/prediction/AppPredictor.java
@@ -96,7 +96,7 @@ public final class AppPredictor {
IBinder b = ServiceManager.getService(Context.APP_PREDICTION_SERVICE);
mPredictionManager = IPredictionManager.Stub.asInterface(b);
mSessionId = new AppPredictionSessionId(
- context.getPackageName() + ":" + UUID.randomUUID().toString());
+ context.getPackageName() + ":" + UUID.randomUUID().toString(), context.getUserId());
try {
mPredictionManager.createPredictionSession(predictionContext, mSessionId);
} catch (RemoteException e) {
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index d8c653c6d0d5..b672a0857cca 100644
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -643,8 +643,9 @@ public final class BluetoothA2dp implements BluetoothProfile {
@SystemApi
@Nullable
@RequiresPermission(Manifest.permission.BLUETOOTH)
- public BluetoothCodecStatus getCodecStatus(@Nullable BluetoothDevice device) {
+ public BluetoothCodecStatus getCodecStatus(@NonNull BluetoothDevice device) {
if (DBG) Log.d(TAG, "getCodecStatus(" + device + ")");
+ verifyDeviceNotNull(device, "getCodecStatus");
try {
final IBluetoothA2dp service = getService();
if (service != null && isEnabled()) {
@@ -670,9 +671,14 @@ public final class BluetoothA2dp implements BluetoothProfile {
*/
@SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
- public void setCodecConfigPreference(@Nullable BluetoothDevice device,
- @Nullable BluetoothCodecConfig codecConfig) {
+ public void setCodecConfigPreference(@NonNull BluetoothDevice device,
+ @NonNull BluetoothCodecConfig codecConfig) {
if (DBG) Log.d(TAG, "setCodecConfigPreference(" + device + ")");
+ verifyDeviceNotNull(device, "setCodecConfigPreference");
+ if (codecConfig == null) {
+ Log.e(TAG, "setCodecConfigPreference: Codec config can't be null");
+ throw new IllegalArgumentException("codecConfig cannot be null");
+ }
try {
final IBluetoothA2dp service = getService();
if (service != null && isEnabled()) {
@@ -695,8 +701,9 @@ public final class BluetoothA2dp implements BluetoothProfile {
*/
@SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
- public void enableOptionalCodecs(@Nullable BluetoothDevice device) {
+ public void enableOptionalCodecs(@NonNull BluetoothDevice device) {
if (DBG) Log.d(TAG, "enableOptionalCodecs(" + device + ")");
+ verifyDeviceNotNull(device, "enableOptionalCodecs");
enableDisableOptionalCodecs(device, true);
}
@@ -709,8 +716,9 @@ public final class BluetoothA2dp implements BluetoothProfile {
*/
@SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
- public void disableOptionalCodecs(@Nullable BluetoothDevice device) {
+ public void disableOptionalCodecs(@NonNull BluetoothDevice device) {
if (DBG) Log.d(TAG, "disableOptionalCodecs(" + device + ")");
+ verifyDeviceNotNull(device, "disableOptionalCodecs");
enableDisableOptionalCodecs(device, false);
}
@@ -750,7 +758,8 @@ public final class BluetoothA2dp implements BluetoothProfile {
@SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
@OptionalCodecsSupportStatus
- public int supportsOptionalCodecs(@Nullable BluetoothDevice device) {
+ public int isOptionalCodecsSupported(@NonNull BluetoothDevice device) {
+ verifyDeviceNotNull(device, "isOptionalCodecsSupported");
try {
final IBluetoothA2dp service = getService();
if (service != null && isEnabled() && isValidDevice(device)) {
@@ -775,7 +784,8 @@ public final class BluetoothA2dp implements BluetoothProfile {
@SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
@OptionalCodecsPreferenceStatus
- public int getOptionalCodecsEnabled(@Nullable BluetoothDevice device) {
+ public int isOptionalCodecsEnabled(@NonNull BluetoothDevice device) {
+ verifyDeviceNotNull(device, "isOptionalCodecsEnabled");
try {
final IBluetoothA2dp service = getService();
if (service != null && isEnabled() && isValidDevice(device)) {
@@ -800,8 +810,9 @@ public final class BluetoothA2dp implements BluetoothProfile {
*/
@SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
- public void setOptionalCodecsEnabled(@Nullable BluetoothDevice device,
+ public void setOptionalCodecsEnabled(@NonNull BluetoothDevice device,
@OptionalCodecsPreferenceStatus int value) {
+ verifyDeviceNotNull(device, "setOptionalCodecsEnabled");
try {
if (value != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN
&& value != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED
@@ -854,6 +865,13 @@ public final class BluetoothA2dp implements BluetoothProfile {
return false;
}
+ private void verifyDeviceNotNull(BluetoothDevice device, String methodName) {
+ if (device == null) {
+ Log.e(TAG, methodName + ": device param is null");
+ throw new IllegalArgumentException("Device cannot be null");
+ }
+ }
+
private boolean isValidDevice(BluetoothDevice device) {
if (device == null) return false;
diff --git a/core/java/android/bluetooth/BluetoothA2dpSink.java b/core/java/android/bluetooth/BluetoothA2dpSink.java
index ee2cc6d14712..ab492309ba3d 100755
--- a/core/java/android/bluetooth/BluetoothA2dpSink.java
+++ b/core/java/android/bluetooth/BluetoothA2dpSink.java
@@ -66,7 +66,7 @@ public final class BluetoothA2dpSink implements BluetoothProfile {
*/
@SystemApi
@SuppressLint("ActionValue")
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
public static final String ACTION_CONNECTION_STATE_CHANGED =
"android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED";
@@ -296,7 +296,7 @@ public final class BluetoothA2dpSink implements BluetoothProfile {
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
public boolean setConnectionPolicy(@Nullable BluetoothDevice device,
@ConnectionPolicy int connectionPolicy) {
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
@@ -345,7 +345,7 @@ public final class BluetoothA2dpSink implements BluetoothProfile {
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
public @ConnectionPolicy int getConnectionPolicy(@Nullable BluetoothDevice device) {
if (VDBG) log("getConnectionPolicy(" + device + ")");
final IBluetoothA2dpSink service = getService();
@@ -370,7 +370,7 @@ public final class BluetoothA2dpSink implements BluetoothProfile {
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
public boolean isAudioPlaying(@Nullable BluetoothDevice device) {
final IBluetoothA2dpSink service = getService();
if (service != null && isEnabled() && isValidDevice(device)) {
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 587c92e014c4..66bfcbd27ca6 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -1970,6 +1970,38 @@ public final class BluetoothAdapter {
}
}
+ private static final String BLUETOOTH_FILTERING_CACHE_PROPERTY =
+ "cache_key.bluetooth.is_offloaded_filtering_supported";
+ private final PropertyInvalidatedCache<Void, Boolean> mBluetoothFilteringCache =
+ new PropertyInvalidatedCache<Void, Boolean>(
+ 8, BLUETOOTH_FILTERING_CACHE_PROPERTY) {
+ @Override
+ protected Boolean recompute(Void query) {
+ try {
+ mServiceLock.readLock().lock();
+ if (mService != null) {
+ return mService.isOffloadedFilteringSupported();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to get isOffloadedFilteringSupported, error: ", e);
+ } finally {
+ mServiceLock.readLock().unlock();
+ }
+ return false;
+
+ }
+ };
+
+ /** @hide */
+ public void disableIsOffloadedFilteringSupportedCache() {
+ mBluetoothFilteringCache.disableLocal();
+ }
+
+ /** @hide */
+ public static void invalidateIsOffloadedFilteringSupportedCache() {
+ PropertyInvalidatedCache.invalidateCache(BLUETOOTH_FILTERING_CACHE_PROPERTY);
+ }
+
/**
* Return true if offloaded filters are supported
*
@@ -1979,17 +2011,7 @@ public final class BluetoothAdapter {
if (!getLeAccess()) {
return false;
}
- try {
- mServiceLock.readLock().lock();
- if (mService != null) {
- return mService.isOffloadedFilteringSupported();
- }
- } catch (RemoteException e) {
- Log.e(TAG, "failed to get isOffloadedFilteringSupported, error: ", e);
- } finally {
- mServiceLock.readLock().unlock();
- }
- return false;
+ return mBluetoothFilteringCache.query(null);
}
/**
@@ -2361,6 +2383,43 @@ public final class BluetoothAdapter {
return BluetoothAdapter.STATE_DISCONNECTED;
}
+ private static final String BLUETOOTH_PROFILE_CACHE_PROPERTY =
+ "cache_key.bluetooth.get_profile_connection_state";
+ private final PropertyInvalidatedCache<Integer, Integer>
+ mGetProfileConnectionStateCache =
+ new PropertyInvalidatedCache<Integer, Integer>(
+ 8, BLUETOOTH_PROFILE_CACHE_PROPERTY) {
+ @Override
+ protected Integer recompute(Integer query) {
+ try {
+ mServiceLock.readLock().lock();
+ if (mService != null) {
+ return mService.getProfileConnectionState(query);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "getProfileConnectionState:", e);
+ } finally {
+ mServiceLock.readLock().unlock();
+ }
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+ @Override
+ public String queryToString(Integer query) {
+ return String.format("getProfileConnectionState(profile=\"%d\")",
+ query);
+ }
+ };
+
+ /** @hide */
+ public void disableGetProfileConnectionStateCache() {
+ mGetProfileConnectionStateCache.disableLocal();
+ }
+
+ /** @hide */
+ public static void invalidateGetProfileConnectionStateCache() {
+ PropertyInvalidatedCache.invalidateCache(BLUETOOTH_PROFILE_CACHE_PROPERTY);
+ }
+
/**
* Get the current connection state of a profile.
* This function can be used to check whether the local Bluetooth adapter
@@ -2378,17 +2437,7 @@ public final class BluetoothAdapter {
if (getState() != STATE_ON) {
return BluetoothProfile.STATE_DISCONNECTED;
}
- try {
- mServiceLock.readLock().lock();
- if (mService != null) {
- return mService.getProfileConnectionState(profile);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "getProfileConnectionState:", e);
- } finally {
- mServiceLock.readLock().unlock();
- }
- return BluetoothProfile.STATE_DISCONNECTED;
+ return mGetProfileConnectionStateCache.query(new Integer(profile));
}
/**
diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java
index e07ca521e77d..1f89ddf0afc7 100644
--- a/core/java/android/bluetooth/BluetoothPbap.java
+++ b/core/java/android/bluetooth/BluetoothPbap.java
@@ -38,9 +38,6 @@ import java.util.Arrays;
import java.util.List;
/**
- * The Android Bluetooth API is not finalized, and *will* change. Use at your
- * own risk.
- *
* Public API for controlling the Bluetooth Pbap Service. This includes
* Bluetooth Phone book Access profile.
* BluetoothPbap is a proxy object for controlling the Bluetooth Pbap
@@ -56,6 +53,11 @@ import java.util.List;
* notification when it is bound, this is especially important if you wish to
* immediately call methods on BluetoothPbap after construction.
*
+ * To get an instance of the BluetoothPbap class, you can call
+ * {@link BluetoothAdapter#getProfileProxy(Context, ServiceListener, int)} with the final param
+ * being {@link BluetoothProfile#PBAP}. The ServiceListener should be able to get the instance of
+ * BluetoothPbap in {@link android.bluetooth.BluetoothProfile.ServiceListener#onServiceConnected}.
+ *
* Android only supports one connected Bluetooth Pce at a time.
*
* @hide
@@ -87,6 +89,7 @@ public class BluetoothPbap implements BluetoothProfile {
*/
@SuppressLint("ActionValue")
@SystemApi
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
@SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_CONNECTION_STATE_CHANGED =
"android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
@@ -235,7 +238,8 @@ public class BluetoothPbap implements BluetoothProfile {
*/
@SystemApi
@Override
- public int getConnectionState(@Nullable BluetoothDevice device) {
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public @BtProfileState int getConnectionState(@Nullable BluetoothDevice device) {
log("getConnectionState: device=" + device);
try {
final IBluetoothPbap service = mService;
@@ -287,7 +291,7 @@ public class BluetoothPbap implements BluetoothProfile {
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
@ConnectionPolicy int connectionPolicy) {
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 0e0161ff4e9f..f32a4ab43357 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -700,27 +700,6 @@ public abstract class ContentResolver implements ContentInterface {
/** @hide */
public static final String REMOTE_CALLBACK_RESULT = "result";
- /**
- * How long we wait for an attached process to publish its content providers
- * before we decide it must be hung.
- * @hide
- */
- public static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS = 10 * 1000;
-
- /**
- * How long we wait for an provider to be published. Should be longer than
- * {@link #CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS}.
- * @hide
- */
- public static final int CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS =
- CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS + 10 * 1000;
-
- // Should be >= {@link #CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS}, because that's how
- // long ActivityManagerService is giving a content provider to get published if a new process
- // needs to be started for that.
- private static final int GET_TYPE_TIMEOUT_MILLIS =
- CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS + 5 * 1000;
-
public ContentResolver(@Nullable Context context) {
this(context, null);
}
@@ -870,6 +849,8 @@ public abstract class ContentResolver implements ContentInterface {
}
}
+ private static final int GET_TYPE_TIMEOUT_MILLIS = 3000;
+
private static class GetTypeResultListener implements RemoteCallback.OnResultListener {
@GuardedBy("this")
public boolean done;
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 6f8a99fce897..0f88c9040d71 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -4289,7 +4289,9 @@ public class Intent implements Parcelable, Cloneable {
* intent filter in their manifests, so that they can be looked up and bound to by
* {@code DataLoaderManagerService}.
*
- * Data loader service providers must be privileged apps.
+ * <p class="note">This is a protected intent that can only be sent by the system.
+ *
+ * Data loader service providers must be privileged apps.
* See {@link com.android.server.pm.PackageManagerShellCommandDataLoader} as an example of such
* data loader service provider.
*
@@ -4970,11 +4972,14 @@ public class Intent implements Parcelable, Cloneable {
* <pre>
* &lt;accessibility-shortcut-target
* android:description="@string/shortcut_target_description"
- * android:summary="@string/shortcut_target_summary" /&gt;
+ * android:summary="@string/shortcut_target_summary"
+ * android:animatedImageDrawable="@drawable/shortcut_target_animated_image"
+ * android:htmlDescription="@string/shortcut_target_html_description" /&gt;
* </pre>
* <p>
* Both description and summary are necessary. The system will ignore the accessibility
- * shortcut target if they are missing.
+ * shortcut target if they are missing. The animated image and html description are supported
+ * to help users understand how to use the shortcut target.
* </p>
*/
@SdkConstant(SdkConstantType.INTENT_CATEGORY)
diff --git a/core/java/android/content/integrity/AppInstallMetadata.java b/core/java/android/content/integrity/AppInstallMetadata.java
index cd5117be6123..4be7e6df46e0 100644
--- a/core/java/android/content/integrity/AppInstallMetadata.java
+++ b/core/java/android/content/integrity/AppInstallMetadata.java
@@ -18,7 +18,9 @@ package android.content.integrity;
import android.annotation.NonNull;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
/**
@@ -40,6 +42,7 @@ public final class AppInstallMetadata {
private final List<String> mInstallerCertificates;
private final long mVersionCode;
private final boolean mIsPreInstalled;
+ private final Map<String, String> mAllowedInstallersAndCertificates;
private AppInstallMetadata(Builder builder) {
this.mPackageName = builder.mPackageName;
@@ -48,6 +51,7 @@ public final class AppInstallMetadata {
this.mInstallerCertificates = builder.mInstallerCertificates;
this.mVersionCode = builder.mVersionCode;
this.mIsPreInstalled = builder.mIsPreInstalled;
+ this.mAllowedInstallersAndCertificates = builder.mAllowedInstallersAndCertificates;
}
@NonNull
@@ -80,6 +84,13 @@ public final class AppInstallMetadata {
return mIsPreInstalled;
}
+ /**
+ * Get the allowed installers and their corresponding cert.
+ */
+ public Map<String, String> getAllowedInstallersAndCertificates() {
+ return mAllowedInstallersAndCertificates;
+ }
+
@Override
public String toString() {
return String.format(
@@ -101,6 +112,23 @@ public final class AppInstallMetadata {
private List<String> mInstallerCertificates;
private long mVersionCode;
private boolean mIsPreInstalled;
+ private Map<String, String> mAllowedInstallersAndCertificates;
+
+ public Builder() {
+ mAllowedInstallersAndCertificates = new HashMap<>();
+ }
+
+ /**
+ * Add allowed installers and cert.
+ *
+ * @see AppInstallMetadata#getAllowedInstallersAndCertificates()
+ */
+ @NonNull
+ public Builder setAllowedInstallersAndCert(
+ @NonNull Map<String, String> allowedInstallersAndCertificates) {
+ this.mAllowedInstallersAndCertificates = allowedInstallersAndCertificates;
+ return this;
+ }
/**
* Set package name of the app to be installed.
diff --git a/core/java/android/content/integrity/AtomicFormula.java b/core/java/android/content/integrity/AtomicFormula.java
index 42459779e212..d911eabc3b83 100644
--- a/core/java/android/content/integrity/AtomicFormula.java
+++ b/core/java/android/content/integrity/AtomicFormula.java
@@ -55,14 +55,12 @@ public abstract class AtomicFormula extends IntegrityFormula {
PRE_INSTALLED,
})
@Retention(RetentionPolicy.SOURCE)
- public @interface Key {
- }
+ public @interface Key {}
/** @hide */
@IntDef(value = {EQ, GT, GTE})
@Retention(RetentionPolicy.SOURCE)
- public @interface Operator {
- }
+ public @interface Operator {}
/**
* Package name of the app.
@@ -354,7 +352,8 @@ public abstract class AtomicFormula extends IntegrityFormula {
"Key %s cannot be used with StringAtomicFormula", keyToString(key)));
mValue = hashValue(key, value);
mIsHashedValue =
- key == APP_CERTIFICATE || key == INSTALLER_CERTIFICATE
+ key == APP_CERTIFICATE
+ || key == INSTALLER_CERTIFICATE
? true
: !mValue.equals(value);
}
diff --git a/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java b/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java
new file mode 100644
index 000000000000..475f019e7b26
--- /dev/null
+++ b/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.integrity;
+
+import java.util.Map;
+
+/**
+ * An atomic formula that evaluates to true if the installer of the current install is specified in
+ * the "allowed installer" field in the android manifest. Note that an empty "allowed installer" by
+ * default means containing all possible installers.
+ *
+ * @hide
+ */
+public class InstallerAllowedByManifestFormula extends IntegrityFormula {
+
+ @Override
+ public int getTag() {
+ return IntegrityFormula.INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG;
+ }
+
+ @Override
+ public boolean matches(AppInstallMetadata appInstallMetadata) {
+ Map<String, String> allowedInstallersAndCertificates =
+ appInstallMetadata.getAllowedInstallersAndCertificates();
+ return allowedInstallersAndCertificates.isEmpty()
+ || installerInAllowedInstallersFromManifest(
+ appInstallMetadata, allowedInstallersAndCertificates);
+ }
+
+ @Override
+ public boolean isAppCertificateFormula() {
+ return false;
+ }
+
+ @Override
+ public boolean isInstallerFormula() {
+ return true;
+ }
+
+ private static boolean installerInAllowedInstallersFromManifest(
+ AppInstallMetadata appInstallMetadata,
+ Map<String, String> allowedInstallersAndCertificates) {
+ return allowedInstallersAndCertificates.containsKey(appInstallMetadata.getInstallerName())
+ && appInstallMetadata.getInstallerCertificates()
+ .contains(
+ allowedInstallersAndCertificates
+ .get(appInstallMetadata.getInstallerName()));
+ }
+}
diff --git a/core/java/android/content/integrity/IntegrityFormula.java b/core/java/android/content/integrity/IntegrityFormula.java
index a2d937e4df31..ac4c9071f755 100644
--- a/core/java/android/content/integrity/IntegrityFormula.java
+++ b/core/java/android/content/integrity/IntegrityFormula.java
@@ -42,66 +42,88 @@ import java.util.Arrays;
@VisibleForTesting
public abstract class IntegrityFormula {
- /**
- * A static formula base for package name formulas.
- *
- * This formulation is incomplete and should always be used with {@code equals} formulation.
- * Evaluates to false when used directly and cannot be written as a parcel.
- */
- @NonNull
- public static final IntegrityFormula PACKAGE_NAME =
- new StringAtomicFormula(AtomicFormula.PACKAGE_NAME);
+ /** Factory class for creating integrity formulas based on the app being installed. */
+ public static final class Application {
+ /** Returns an integrity formula that checks the equality to a package name. */
+ @NonNull
+ public static IntegrityFormula packageNameEquals(@NonNull String packageName) {
+ return new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, packageName);
+ }
- /**
- * A static formula base for app certificate formulas.
- *
- * This formulation is incomplete and should always be used with {@code equals} formulation.
- * Evaluates to false when used directly and cannot be written as a parcel.
- */
- @NonNull
- public static final IntegrityFormula APP_CERTIFICATE =
- new StringAtomicFormula(AtomicFormula.APP_CERTIFICATE);
+ /**
+ * Returns an integrity formula that checks if the app certificates contain {@code
+ * appCertificate}.
+ */
+ @NonNull
+ public static IntegrityFormula certificatesContain(@NonNull String appCertificate) {
+ return new StringAtomicFormula(AtomicFormula.APP_CERTIFICATE, appCertificate);
+ }
- /**
- * A static formula base for installer name formulas.
- *
- * This formulation is incomplete and should always be used with {@code equals} formulation.
- * Evaluates to false when used directly and cannot be written as a parcel.
- */
- @NonNull
- public static final IntegrityFormula INSTALLER_NAME =
- new StringAtomicFormula(AtomicFormula.INSTALLER_NAME);
+ /** Returns an integrity formula that checks the equality to a version code. */
+ @NonNull
+ public static IntegrityFormula versionCodeEquals(@NonNull long versionCode) {
+ return new LongAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.EQ, versionCode);
+ }
- /**
- * A static formula base for installer certificate formulas.
- *
- * This formulation is incomplete and should always be used with {@code equals} formulation.
- * Evaluates to false when used directly and cannot be written as a parcel.
- */
- @NonNull
- public static final IntegrityFormula INSTALLER_CERTIFICATE =
- new StringAtomicFormula(AtomicFormula.INSTALLER_CERTIFICATE);
+ /**
+ * Returns an integrity formula that checks the app's version code is greater than the
+ * provided value.
+ */
+ @NonNull
+ public static IntegrityFormula versionCodeGreaterThan(@NonNull long versionCode) {
+ return new LongAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.GT, versionCode);
+ }
- /**
- * A static formula base for version code name formulas.
- *
- * This formulation is incomplete and should always be used with {@code equals},
- * {@code greaterThan} and {@code greaterThanEquals} formulation. Evaluates to false when used
- * directly and cannot be written as a parcel.
- */
- @NonNull
- public static final IntegrityFormula VERSION_CODE =
- new LongAtomicFormula(AtomicFormula.VERSION_CODE);
+ /**
+ * Returns an integrity formula that checks the app's version code is greater than or equal
+ * to the provided value.
+ */
+ @NonNull
+ public static IntegrityFormula versionCodeGreaterThanOrEqualTo(@NonNull long versionCode) {
+ return new LongAtomicFormula(
+ AtomicFormula.VERSION_CODE, AtomicFormula.GTE, versionCode);
+ }
- /**
- * A static formula base for pre-installed status formulas.
- *
- * This formulation is incomplete and should always be used with {@code equals} formulation.
- * Evaluates to false when used directly and cannot be written as a parcel.
- */
- @NonNull
- public static final IntegrityFormula PRE_INSTALLED =
- new BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED);
+ /** Returns an integrity formula that is valid when app is pre-installed. */
+ @NonNull
+ public static IntegrityFormula isPreInstalled() {
+ return new BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true);
+ }
+
+ private Application() {
+ }
+ }
+
+ /** Factory class for creating integrity formulas based on installer. */
+ public static final class Installer {
+ /** Returns an integrity formula that checks the equality to an installer name. */
+ @NonNull
+ public static IntegrityFormula packageNameEquals(@NonNull String installerName) {
+ return new StringAtomicFormula(AtomicFormula.INSTALLER_NAME, installerName);
+ }
+
+ /**
+ * An static formula that evaluates to true if the installer is NOT allowed according to the
+ * "allowed installer" field in the android manifest.
+ */
+ @NonNull
+ public static IntegrityFormula notAllowedByManifest() {
+ return not(new InstallerAllowedByManifestFormula());
+ }
+
+ /**
+ * Returns an integrity formula that checks if the installer certificates contain {@code
+ * installerCertificate}.
+ */
+ @NonNull
+ public static IntegrityFormula certificatesContain(@NonNull String installerCertificate) {
+ return new StringAtomicFormula(AtomicFormula.INSTALLER_CERTIFICATE,
+ installerCertificate);
+ }
+
+ private Installer() {
+ }
+ }
/** @hide */
@IntDef(
@@ -109,10 +131,12 @@ public abstract class IntegrityFormula {
COMPOUND_FORMULA_TAG,
STRING_ATOMIC_FORMULA_TAG,
LONG_ATOMIC_FORMULA_TAG,
- BOOLEAN_ATOMIC_FORMULA_TAG
+ BOOLEAN_ATOMIC_FORMULA_TAG,
+ INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG
})
@Retention(RetentionPolicy.SOURCE)
- @interface Tag {}
+ @interface Tag {
+ }
/** @hide */
public static final int COMPOUND_FORMULA_TAG = 0;
@@ -122,6 +146,8 @@ public abstract class IntegrityFormula {
public static final int LONG_ATOMIC_FORMULA_TAG = 2;
/** @hide */
public static final int BOOLEAN_ATOMIC_FORMULA_TAG = 3;
+ /** @hide */
+ public static final int INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG = 4;
/**
* Returns the tag that identifies the current class.
@@ -135,14 +161,14 @@ public abstract class IntegrityFormula {
*
* @hide
*/
- public abstract @Tag boolean matches(AppInstallMetadata appInstallMetadata);
+ public abstract boolean matches(AppInstallMetadata appInstallMetadata);
/**
* Returns true when the formula (or one of its atomic formulas) has app certificate as key.
*
* @hide
*/
- public abstract @Tag boolean isAppCertificateFormula();
+ public abstract boolean isAppCertificateFormula();
/**
* Returns true when the formula (or one of its atomic formulas) has installer package name
@@ -150,7 +176,7 @@ public abstract class IntegrityFormula {
*
* @hide
*/
- public abstract @Tag boolean isInstallerFormula();
+ public abstract boolean isInstallerFormula();
/**
* Write an {@link IntegrityFormula} to {@link android.os.Parcel}.
@@ -159,7 +185,6 @@ public abstract class IntegrityFormula {
* {@link Parcelable}.
*
* @throws IllegalArgumentException if {@link IntegrityFormula} is not a recognized subclass
- *
* @hide
*/
public static void writeToParcel(
@@ -195,70 +220,6 @@ public abstract class IntegrityFormula {
}
/**
- * Returns an integrity formula that evaluates to true when value of the key matches to the
- * provided string value.
- *
- * <p>The value will be hashed with SHA256 and the hex digest will be computed; for
- * all cases except when the key is PACKAGE_NAME or INSTALLER_NAME and the value is less than
- * 32 characters.
- *
- * <p>Throws an {@link IllegalArgumentException} if the key is not string typed.
- */
- @NonNull
- public IntegrityFormula equalTo(@NonNull String value) {
- AtomicFormula baseFormula = (AtomicFormula) this;
- return new AtomicFormula.StringAtomicFormula(baseFormula.getKey(), value);
- }
-
- /**
- * Returns an integrity formula that evaluates to true when the boolean value of the key matches
- * the provided boolean value. It can only be used with the boolean comparison keys.
- *
- * <p>Throws an {@link IllegalArgumentException} if the key is not boolean typed.
- */
- @NonNull
- public IntegrityFormula equalTo(boolean value) {
- AtomicFormula baseFormula = (AtomicFormula) this;
- return new AtomicFormula.BooleanAtomicFormula(baseFormula.getKey(), value);
- }
-
- /**
- * Returns a formula that evaluates to true when the value of the key in the package being
- * installed is equal to {@code value}.
- *
- * <p>Throws an {@link IllegalArgumentException} if the key is not long typed.
- */
- @NonNull
- public IntegrityFormula equalTo(long value) {
- AtomicFormula baseFormula = (AtomicFormula) this;
- return new AtomicFormula.LongAtomicFormula(baseFormula.getKey(), AtomicFormula.EQ, value);
- }
-
- /**
- * Returns a formula that evaluates to true when the value of the key in the package being
- * installed is greater than {@code value}.
- *
- * <p>Throws an {@link IllegalArgumentException} if the key is not long typed.
- */
- @NonNull
- public IntegrityFormula greaterThan(long value) {
- AtomicFormula baseFormula = (AtomicFormula) this;
- return new AtomicFormula.LongAtomicFormula(baseFormula.getKey(), AtomicFormula.GT, value);
- }
-
- /**
- * Returns a formula that evaluates to true when the value of the key in the package being
- * installed is greater than or equals to the {@code value}.
- *
- * <p>Throws an {@link IllegalArgumentException} if the key is not long typed.
- */
- @NonNull
- public IntegrityFormula greaterThanOrEquals(long value) {
- AtomicFormula baseFormula = (AtomicFormula) this;
- return new AtomicFormula.LongAtomicFormula(baseFormula.getKey(), AtomicFormula.GTE, value);
- }
-
- /**
* Returns a formula that evaluates to true when any formula in {@code formulae} evaluates to
* true.
*
diff --git a/core/java/android/content/pm/InstallationFile.java b/core/java/android/content/pm/InstallationFile.java
index 111ad32d1e41..b449945628d2 100644
--- a/core/java/android/content/pm/InstallationFile.java
+++ b/core/java/android/content/pm/InstallationFile.java
@@ -16,82 +16,59 @@
package android.content.pm;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
/**
* Defines the properties of a file in an installation session.
- * TODO(b/136132412): update with new APIs.
- *
* @hide
*/
@SystemApi
public final class InstallationFile implements Parcelable {
- public static final int FILE_TYPE_UNKNOWN = -1;
- public static final int FILE_TYPE_APK = 0;
- public static final int FILE_TYPE_LIB = 1;
- public static final int FILE_TYPE_OBB = 2;
-
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = {"FILE_TYPE_"}, value = {
- FILE_TYPE_APK,
- FILE_TYPE_LIB,
- FILE_TYPE_OBB,
- })
- public @interface FileType {
- }
-
- private String mFileName;
- private @FileType int mFileType;
- private long mFileSize;
- private byte[] mMetadata;
-
- public InstallationFile(@NonNull String fileName, long fileSize,
- @Nullable byte[] metadata) {
- mFileName = fileName;
- mFileSize = fileSize;
+ private final @PackageInstaller.FileLocation int mLocation;
+ private final @NonNull String mName;
+ private final long mLengthBytes;
+ private final @Nullable byte[] mMetadata;
+ private final @Nullable byte[] mSignature;
+
+ public InstallationFile(@PackageInstaller.FileLocation int location, @NonNull String name,
+ long lengthBytes, @Nullable byte[] metadata, @Nullable byte[] signature) {
+ mLocation = location;
+ mName = name;
+ mLengthBytes = lengthBytes;
mMetadata = metadata;
- if (fileName.toLowerCase().endsWith(".apk")) {
- mFileType = FILE_TYPE_APK;
- } else if (fileName.toLowerCase().endsWith(".obb")) {
- mFileType = FILE_TYPE_OBB;
- } else if (fileName.toLowerCase().endsWith(".so") && fileName.toLowerCase().startsWith(
- "lib/")) {
- mFileType = FILE_TYPE_LIB;
- } else {
- mFileType = FILE_TYPE_UNKNOWN;
- }
+ mSignature = signature;
}
- public @FileType int getFileType() {
- return mFileType;
+ public @PackageInstaller.FileLocation int getLocation() {
+ return mLocation;
}
public @NonNull String getName() {
- return mFileName;
+ return mName;
}
- public long getSize() {
- return mFileSize;
+ public long getLengthBytes() {
+ return mLengthBytes;
}
public @Nullable byte[] getMetadata() {
return mMetadata;
}
+ public @Nullable byte[] getSignature() {
+ return mSignature;
+ }
+
private InstallationFile(Parcel source) {
- mFileName = source.readString();
- mFileType = source.readInt();
- mFileSize = source.readLong();
+ mLocation = source.readInt();
+ mName = source.readString();
+ mLengthBytes = source.readLong();
mMetadata = source.createByteArray();
+ mSignature = source.createByteArray();
}
@Override
@@ -101,10 +78,11 @@ public final class InstallationFile implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeString(mFileName);
- dest.writeInt(mFileType);
- dest.writeLong(mFileSize);
+ dest.writeInt(mLocation);
+ dest.writeString(mName);
+ dest.writeLong(mLengthBytes);
dest.writeByteArray(mMetadata);
+ dest.writeByteArray(mSignature);
}
public static final @NonNull Creator<InstallationFile> CREATOR =
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index d2532783f47c..70603b472551 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -16,6 +16,7 @@
package android.content.pm;
+import static android.Manifest.permission;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -601,11 +602,15 @@ public class LauncherApps {
}
/**
- * Show an error log on logcat, when the calling user is a managed profile, and the target
- * user is different from the calling user, in order to help developers to detect it.
+ * Show an error log on logcat, when the calling user is a managed profile, the target
+ * user is different from the calling user, and it is not called from a package that has the
+ * {@link permission.INTERACT_ACROSS_USERS_FULL} permission, in order to help
+ * developers to detect it.
*/
private void logErrorForInvalidProfileAccess(@NonNull UserHandle target) {
- if (UserHandle.myUserId() != target.getIdentifier() && mUserManager.isManagedProfile()) {
+ if (UserHandle.myUserId() != target.getIdentifier() && mUserManager.isManagedProfile()
+ && mContext.checkSelfPermission(permission.INTERACT_ACROSS_USERS_FULL)
+ != PackageManager.PERMISSION_GRANTED) {
Log.w(TAG, "Accessing other profiles/users from managed profile is no longer allowed.");
}
}
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 8c358cc522d6..6a9e0aa047d1 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -45,6 +45,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
+import android.app.UiModeManager;
import android.app.WindowConfiguration;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.LocaleProto;
@@ -1975,6 +1976,15 @@ public final class Configuration implements Parcelable, Comparable<Configuration
readFromParcel(source);
}
+
+ /**
+ * Retuns whether the configuration is in night mode
+ * @return true if night mode is active and false otherwise
+ */
+ public boolean isNightModeActive() {
+ return (uiMode & UI_MODE_NIGHT_MASK) == UI_MODE_NIGHT_YES;
+ }
+
public int compareTo(Configuration that) {
int n;
float a = this.fontScale;
diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java
index 5a1365169eee..add67aa436c6 100644
--- a/core/java/android/hardware/biometrics/BiometricConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricConstants.java
@@ -132,6 +132,14 @@ public interface BiometricConstants {
int BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL = 14;
/**
+ * A security vulnerability has been discovered and the sensor is unavailable until a
+ * security update has addressed this issue. This error can be received if for example,
+ * authentication was requested with {@link Authenticators#BIOMETRIC_STRONG}, but the
+ * sensor's strength can currently only meet {@link Authenticators#BIOMETRIC_WEAK}.
+ */
+ int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15;
+
+ /**
* This constant is only used by SystemUI. It notifies SystemUI that authentication was paused
* because the authentication attempt was unsuccessful.
* @hide
diff --git a/core/java/android/hardware/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
index bae0fd3ad3b9..eafcf529de62 100644
--- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
@@ -17,6 +17,7 @@
package android.hardware.biometrics;
import android.app.KeyguardManager;
+import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.face.FaceManager;
/**
@@ -36,12 +37,12 @@ public interface BiometricFaceConstants {
* authentication. Note this is to accommodate people who have limited
* vision.
*/
- public static final int FEATURE_REQUIRE_ATTENTION = 1;
+ int FEATURE_REQUIRE_ATTENTION = 1;
/**
* Require a diverse set of poses during enrollment. Note this is to
* accommodate people with limited mobility.
*/
- public static final int FEATURE_REQUIRE_REQUIRE_DIVERSITY = 2;
+ int FEATURE_REQUIRE_REQUIRE_DIVERSITY = 2;
//
// Error messages from face authentication hardware during initialization, enrollment,
@@ -50,32 +51,32 @@ public interface BiometricFaceConstants {
/**
* The hardware is unavailable. Try again later.
*/
- public static final int FACE_ERROR_HW_UNAVAILABLE = 1;
+ int FACE_ERROR_HW_UNAVAILABLE = 1;
/**
* Error state returned when the sensor was unable to process the current image.
*/
- public static final int FACE_ERROR_UNABLE_TO_PROCESS = 2;
+ int FACE_ERROR_UNABLE_TO_PROCESS = 2;
/**
* Error state returned when the current request has been running too long. This is intended to
* prevent programs from waiting for the face authentication sensor indefinitely. The timeout is
* platform and sensor-specific, but is generally on the order of 30 seconds.
*/
- public static final int FACE_ERROR_TIMEOUT = 3;
+ int FACE_ERROR_TIMEOUT = 3;
/**
* Error state returned for operations like enrollment; the operation cannot be completed
* because there's not enough storage remaining to complete the operation.
*/
- public static final int FACE_ERROR_NO_SPACE = 4;
+ int FACE_ERROR_NO_SPACE = 4;
/**
* The operation was canceled because the face authentication sensor is unavailable. For
* example, this may happen when the user is switched, the device is locked or another pending
* operation prevents or disables it.
*/
- public static final int FACE_ERROR_CANCELED = 5;
+ int FACE_ERROR_CANCELED = 5;
/**
* The {@link FaceManager#remove} call failed. Typically this will happen when the
@@ -83,13 +84,13 @@ public interface BiometricFaceConstants {
*
* @hide
*/
- public static final int FACE_ERROR_UNABLE_TO_REMOVE = 6;
+ int FACE_ERROR_UNABLE_TO_REMOVE = 6;
/**
* The operation was canceled because the API is locked out due to too many attempts.
* This occurs after 5 failed attempts, and lasts for 30 seconds.
*/
- public static final int FACE_ERROR_LOCKOUT = 7;
+ int FACE_ERROR_LOCKOUT = 7;
/**
* Hardware vendors may extend this list if there are conditions that do not fall under one of
@@ -99,52 +100,62 @@ public interface BiometricFaceConstants {
* expected to show the error message string if they happen, but are advised not to rely on the
* message id since they will be device and vendor-specific
*/
- public static final int FACE_ERROR_VENDOR = 8;
+ int FACE_ERROR_VENDOR = 8;
/**
* The operation was canceled because FACE_ERROR_LOCKOUT occurred too many times.
* Face authentication is disabled until the user unlocks with strong authentication
* (PIN/Pattern/Password)
*/
- public static final int FACE_ERROR_LOCKOUT_PERMANENT = 9;
+ int FACE_ERROR_LOCKOUT_PERMANENT = 9;
/**
* The user canceled the operation. Upon receiving this, applications should use alternate
* authentication (e.g. a password). The application should also provide the means to return
* to face authentication, such as a "use face authentication" button.
*/
- public static final int FACE_ERROR_USER_CANCELED = 10;
+ int FACE_ERROR_USER_CANCELED = 10;
/**
* The user does not have a face enrolled.
*/
- public static final int FACE_ERROR_NOT_ENROLLED = 11;
+ int FACE_ERROR_NOT_ENROLLED = 11;
/**
* The device does not have a face sensor. This message will propagate if the calling app
* ignores the result from PackageManager.hasFeature(FEATURE_FACE) and calls
* this API anyway. Apps should always check for the feature before calling this API.
*/
- public static final int FACE_ERROR_HW_NOT_PRESENT = 12;
+ int FACE_ERROR_HW_NOT_PRESENT = 12;
/**
* The user pressed the negative button. This is a placeholder that is currently only used
* by the support library.
+ *
* @hide
*/
- public static final int FACE_ERROR_NEGATIVE_BUTTON = 13;
+ int FACE_ERROR_NEGATIVE_BUTTON = 13;
/**
* The device does not have pin, pattern, or password set up. See
* {@link BiometricPrompt.Builder#setDeviceCredentialAllowed(boolean)} and
* {@link KeyguardManager#isDeviceSecure()}
*/
- public static final int BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL = 14;
+ int BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL = 14;
+
+ /**
+ * A security vulnerability has been discovered and the sensor is unavailable until a
+ * security update has addressed this issue. This error can be received if for example,
+ * authentication was requested with {@link Authenticators#BIOMETRIC_STRONG}, but the
+ * sensor's strength can currently only meet {@link Authenticators#BIOMETRIC_WEAK}.
+ * @hide
+ */
+ int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15;
/**
* @hide
*/
- public static final int FACE_ERROR_VENDOR_BASE = 1000;
+ int FACE_ERROR_VENDOR_BASE = 1000;
//
// Image acquisition messages. These will not be sent to the user, since they conflict with
@@ -154,13 +165,13 @@ public interface BiometricFaceConstants {
/**
* The image acquired was good.
*/
- public static final int FACE_ACQUIRED_GOOD = 0;
+ int FACE_ACQUIRED_GOOD = 0;
/**
* The face image was not good enough to process due to a detected condition.
* (See {@link #FACE_ACQUIRED_TOO_BRIGHT or @link #FACE_ACQUIRED_TOO_DARK}).
*/
- public static final int FACE_ACQUIRED_INSUFFICIENT = 1;
+ int FACE_ACQUIRED_INSUFFICIENT = 1;
/**
* The face image was too bright due to too much ambient light.
@@ -169,7 +180,7 @@ public interface BiometricFaceConstants {
* The user is expected to take action to retry in better lighting conditions
* when this is returned.
*/
- public static final int FACE_ACQUIRED_TOO_BRIGHT = 2;
+ int FACE_ACQUIRED_TOO_BRIGHT = 2;
/**
* The face image was too dark due to illumination light obscured.
@@ -178,65 +189,65 @@ public interface BiometricFaceConstants {
* The user is expected to take action to retry in better lighting conditions
* when this is returned.
*/
- public static final int FACE_ACQUIRED_TOO_DARK = 3;
+ int FACE_ACQUIRED_TOO_DARK = 3;
/**
* The detected face is too close to the sensor, and the image can't be processed.
* The user should be informed to move farther from the sensor when this is returned.
*/
- public static final int FACE_ACQUIRED_TOO_CLOSE = 4;
+ int FACE_ACQUIRED_TOO_CLOSE = 4;
/**
* The detected face is too small, as the user might be too far from the sensor.
* The user should be informed to move closer to the sensor when this is returned.
*/
- public static final int FACE_ACQUIRED_TOO_FAR = 5;
+ int FACE_ACQUIRED_TOO_FAR = 5;
/**
* Only the upper part of the face was detected. The sensor field of view is too high.
* The user should be informed to move up with respect to the sensor when this is returned.
*/
- public static final int FACE_ACQUIRED_TOO_HIGH = 6;
+ int FACE_ACQUIRED_TOO_HIGH = 6;
/**
* Only the lower part of the face was detected. The sensor field of view is too low.
* The user should be informed to move down with respect to the sensor when this is returned.
*/
- public static final int FACE_ACQUIRED_TOO_LOW = 7;
+ int FACE_ACQUIRED_TOO_LOW = 7;
/**
* Only the right part of the face was detected. The sensor field of view is too far right.
* The user should be informed to move to the right with respect to the sensor
* when this is returned.
*/
- public static final int FACE_ACQUIRED_TOO_RIGHT = 8;
+ int FACE_ACQUIRED_TOO_RIGHT = 8;
/**
* Only the left part of the face was detected. The sensor field of view is too far left.
* The user should be informed to move to the left with respect to the sensor
* when this is returned.
*/
- public static final int FACE_ACQUIRED_TOO_LEFT = 9;
+ int FACE_ACQUIRED_TOO_LEFT = 9;
/**
* The user's eyes have strayed away from the sensor. If this message is sent, the user should
* be informed to look at the device. If the user can't be found in the frame, one of the other
* acquisition messages should be sent, e.g. FACE_ACQUIRED_NOT_DETECTED.
*/
- public static final int FACE_ACQUIRED_POOR_GAZE = 10;
+ int FACE_ACQUIRED_POOR_GAZE = 10;
/**
* No face was detected in front of the sensor.
* The user should be informed to point the sensor to a face when this is returned.
*/
- public static final int FACE_ACQUIRED_NOT_DETECTED = 11;
+ int FACE_ACQUIRED_NOT_DETECTED = 11;
/**
* Too much motion was detected.
* The user should be informed to keep their face steady relative to the
* sensor.
*/
- public static final int FACE_ACQUIRED_TOO_MUCH_MOTION = 12;
+ int FACE_ACQUIRED_TOO_MUCH_MOTION = 12;
/**
* The sensor needs to be re-calibrated. This is an unexpected condition, and should only be
@@ -244,20 +255,20 @@ public interface BiometricFaceConstants {
* requires user intervention, e.g. re-enrolling. The expected response to this message is to
* direct the user to re-enroll.
*/
- public static final int FACE_ACQUIRED_RECALIBRATE = 13;
+ int FACE_ACQUIRED_RECALIBRATE = 13;
/**
* The face is too different from a previous acquisition. This condition
* only applies to enrollment. This can happen if the user passes the
* device to someone else in the middle of enrollment.
*/
- public static final int FACE_ACQUIRED_TOO_DIFFERENT = 14;
+ int FACE_ACQUIRED_TOO_DIFFERENT = 14;
/**
* The face is too similar to a previous acquisition. This condition only
* applies to enrollment. The user should change their pose.
*/
- public static final int FACE_ACQUIRED_TOO_SIMILAR = 15;
+ int FACE_ACQUIRED_TOO_SIMILAR = 15;
/**
* The magnitude of the pan angle of the user’s face with respect to the sensor’s
@@ -269,7 +280,7 @@ public interface BiometricFaceConstants {
*
* The user should be informed to look more directly at the camera.
*/
- public static final int FACE_ACQUIRED_PAN_TOO_EXTREME = 16;
+ int FACE_ACQUIRED_PAN_TOO_EXTREME = 16;
/**
* The magnitude of the tilt angle of the user’s face with respect to the sensor’s
@@ -280,7 +291,7 @@ public interface BiometricFaceConstants {
*
* The user should be informed to look more directly at the camera.
*/
- public static final int FACE_ACQUIRED_TILT_TOO_EXTREME = 17;
+ int FACE_ACQUIRED_TILT_TOO_EXTREME = 17;
/**
* The magnitude of the roll angle of the user’s face with respect to the sensor’s
@@ -292,7 +303,7 @@ public interface BiometricFaceConstants {
*
* The user should be informed to look more directly at the camera.
*/
- public static final int FACE_ACQUIRED_ROLL_TOO_EXTREME = 18;
+ int FACE_ACQUIRED_ROLL_TOO_EXTREME = 18;
/**
* The user’s face has been obscured by some object.
@@ -300,7 +311,7 @@ public interface BiometricFaceConstants {
* The user should be informed to remove any objects from the line of sight from
* the sensor to the user’s face.
*/
- public static final int FACE_ACQUIRED_FACE_OBSCURED = 19;
+ int FACE_ACQUIRED_FACE_OBSCURED = 19;
/**
* This message represents the earliest message sent at the beginning of the authentication
@@ -310,12 +321,12 @@ public interface BiometricFaceConstants {
* The framework will measure latency based on the time between the last START message and the
* onAuthenticated callback.
*/
- public static final int FACE_ACQUIRED_START = 20;
+ int FACE_ACQUIRED_START = 20;
/**
* The sensor is dirty. The user should be informed to clean the sensor.
*/
- public static final int FACE_ACQUIRED_SENSOR_DIRTY = 21;
+ int FACE_ACQUIRED_SENSOR_DIRTY = 21;
/**
* Hardware vendors may extend this list if there are conditions that do not fall under one of
@@ -323,10 +334,10 @@ public interface BiometricFaceConstants {
*
* @hide
*/
- public static final int FACE_ACQUIRED_VENDOR = 22;
+ int FACE_ACQUIRED_VENDOR = 22;
/**
* @hide
*/
- public static final int FACE_ACQUIRED_VENDOR_BASE = 1000;
+ int FACE_ACQUIRED_VENDOR_BASE = 1000;
}
diff --git a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
index 5c74456eb60a..46e8cc036809 100644
--- a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
@@ -18,6 +18,7 @@ package android.hardware.biometrics;
import android.app.KeyguardManager;
import android.compat.annotation.UnsupportedAppUsage;
+import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.fingerprint.FingerprintManager;
/**
@@ -37,32 +38,32 @@ public interface BiometricFingerprintConstants {
/**
* The hardware is unavailable. Try again later.
*/
- public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1;
+ int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1;
/**
* Error state returned when the sensor was unable to process the current image.
*/
- public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2;
+ int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2;
/**
* Error state returned when the current request has been running too long. This is intended to
* prevent programs from waiting for the fingerprint sensor indefinitely. The timeout is
* platform and sensor-specific, but is generally on the order of 30 seconds.
*/
- public static final int FINGERPRINT_ERROR_TIMEOUT = 3;
+ int FINGERPRINT_ERROR_TIMEOUT = 3;
/**
* Error state returned for operations like enrollment; the operation cannot be completed
* because there's not enough storage remaining to complete the operation.
*/
- public static final int FINGERPRINT_ERROR_NO_SPACE = 4;
+ int FINGERPRINT_ERROR_NO_SPACE = 4;
/**
* The operation was canceled because the fingerprint sensor is unavailable. For example,
* this may happen when the user is switched, the device is locked or another pending operation
* prevents or disables it.
*/
- public static final int FINGERPRINT_ERROR_CANCELED = 5;
+ int FINGERPRINT_ERROR_CANCELED = 5;
/**
* The {@link FingerprintManager#remove} call failed. Typically this will happen when the
@@ -70,13 +71,13 @@ public interface BiometricFingerprintConstants {
*
* @hide
*/
- public static final int FINGERPRINT_ERROR_UNABLE_TO_REMOVE = 6;
+ int FINGERPRINT_ERROR_UNABLE_TO_REMOVE = 6;
/**
* The operation was canceled because the API is locked out due to too many attempts.
* This occurs after 5 failed attempts, and lasts for 30 seconds.
*/
- public static final int FINGERPRINT_ERROR_LOCKOUT = 7;
+ int FINGERPRINT_ERROR_LOCKOUT = 7;
/**
* Hardware vendors may extend this list if there are conditions that do not fall under one of
@@ -86,52 +87,63 @@ public interface BiometricFingerprintConstants {
* expected to show the error message string if they happen, but are advised not to rely on the
* message id since they will be device and vendor-specific
*/
- public static final int FINGERPRINT_ERROR_VENDOR = 8;
+ int FINGERPRINT_ERROR_VENDOR = 8;
/**
* The operation was canceled because FINGERPRINT_ERROR_LOCKOUT occurred too many times.
* Fingerprint authentication is disabled until the user unlocks with strong authentication
* (PIN/Pattern/Password)
*/
- public static final int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 9;
+ int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 9;
/**
* The user canceled the operation. Upon receiving this, applications should use alternate
* authentication (e.g. a password). The application should also provide the means to return
* to fingerprint authentication, such as a "use fingerprint" button.
*/
- public static final int FINGERPRINT_ERROR_USER_CANCELED = 10;
+ int FINGERPRINT_ERROR_USER_CANCELED = 10;
/**
* The user does not have any fingerprints enrolled.
*/
- public static final int FINGERPRINT_ERROR_NO_FINGERPRINTS = 11;
+ int FINGERPRINT_ERROR_NO_FINGERPRINTS = 11;
/**
* The device does not have a fingerprint sensor.
*/
- public static final int FINGERPRINT_ERROR_HW_NOT_PRESENT = 12;
+ int FINGERPRINT_ERROR_HW_NOT_PRESENT = 12;
/**
* The user pressed the negative button. This is a placeholder that is currently only used
* by the support library.
+ *
* @hide
*/
- public static final int FINGERPRINT_ERROR_NEGATIVE_BUTTON = 13;
+ int FINGERPRINT_ERROR_NEGATIVE_BUTTON = 13;
/**
* The device does not have pin, pattern, or password set up. See
* {@link BiometricPrompt.Builder#setDeviceCredentialAllowed(boolean)} and
* {@link KeyguardManager#isDeviceSecure()}
+ *
* @hide
*/
- public static final int BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL = 14;
+ int BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL = 14;
+
+ /**
+ * A security vulnerability has been discovered and the sensor is unavailable until a
+ * security update has addressed this issue. This error can be received if for example,
+ * authentication was requested with {@link Authenticators#BIOMETRIC_STRONG}, but the
+ * sensor's strength can currently only meet {@link Authenticators#BIOMETRIC_WEAK}.
+ * @hide
+ */
+ public static final int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15;
/**
* @hide
*/
@UnsupportedAppUsage
- public static final int FINGERPRINT_ERROR_VENDOR_BASE = 1000;
+ int FINGERPRINT_ERROR_VENDOR_BASE = 1000;
//
// Image acquisition messages. Must agree with those in fingerprint.h
@@ -140,19 +152,19 @@ public interface BiometricFingerprintConstants {
/**
* The image acquired was good.
*/
- public static final int FINGERPRINT_ACQUIRED_GOOD = 0;
+ int FINGERPRINT_ACQUIRED_GOOD = 0;
/**
* Only a partial fingerprint image was detected. During enrollment, the user should be
* informed on what needs to happen to resolve this problem, e.g. "press firmly on sensor."
*/
- public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1;
+ int FINGERPRINT_ACQUIRED_PARTIAL = 1;
/**
* The fingerprint image was too noisy to process due to a detected condition (i.e. dry skin) or
* a possibly dirty sensor (See {@link #FINGERPRINT_ACQUIRED_IMAGER_DIRTY}).
*/
- public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2;
+ int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2;
/**
* The fingerprint image was too noisy due to suspected or detected dirt on the sensor.
@@ -161,13 +173,13 @@ public interface BiometricFingerprintConstants {
* (stuck pixels, swaths, etc.). The user is expected to take action to clean the sensor
* when this is returned.
*/
- public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3;
+ int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3;
/**
* The fingerprint image was unreadable due to lack of motion. This is most appropriate for
* linear array sensors that require a swipe motion.
*/
- public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 4;
+ int FINGERPRINT_ACQUIRED_TOO_SLOW = 4;
/**
* The fingerprint image was incomplete due to quick motion. While mostly appropriate for
@@ -175,16 +187,29 @@ public interface BiometricFingerprintConstants {
* The user should be asked to move the finger slower (linear) or leave the finger on the sensor
* longer.
*/
- public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 5;
+ int FINGERPRINT_ACQUIRED_TOO_FAST = 5;
/**
* Hardware vendors may extend this list if there are conditions that do not fall under one of
* the above categories. Vendors are responsible for providing error strings for these errors.
+ *
* @hide
*/
- public static final int FINGERPRINT_ACQUIRED_VENDOR = 6;
+ int FINGERPRINT_ACQUIRED_VENDOR = 6;
+
+ /**
+ * This message represents the earliest message sent at the beginning of the authentication
+ * pipeline. It is expected to be used to measure latency. Note this should be sent whenever
+ * authentication is restarted.
+ * The framework will measure latency based on the time between the last START message and the
+ * onAuthenticated callback.
+ *
+ * @hide
+ */
+ int FINGERPRINT_ACQUIRED_START = 7;
+
/**
* @hide
*/
- public static final int FINGERPRINT_ACQUIRED_VENDOR_BASE = 1000;
+ int FINGERPRINT_ACQUIRED_VENDOR_BASE = 1000;
}
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index 125b676e14f7..7d66cae4c845 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -61,10 +61,20 @@ public class BiometricManager {
public static final int BIOMETRIC_ERROR_NO_HARDWARE =
BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT;
+ /**
+ * A security vulnerability has been discovered and the sensor is unavailable until a
+ * security update has addressed this issue. This error can be received if for example,
+ * authentication was requested with {@link Authenticators#BIOMETRIC_STRONG}, but the
+ * sensor's strength can currently only meet {@link Authenticators#BIOMETRIC_WEAK}.
+ */
+ public static final int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED =
+ BiometricConstants.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED;
+
@IntDef({BIOMETRIC_SUCCESS,
BIOMETRIC_ERROR_HW_UNAVAILABLE,
BIOMETRIC_ERROR_NONE_ENROLLED,
- BIOMETRIC_ERROR_NO_HARDWARE})
+ BIOMETRIC_ERROR_NO_HARDWARE,
+ BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED})
@interface BiometricError {}
/**
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index f6717c77f90e..ea576bc569d2 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -918,7 +918,9 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
if (mEnrollmentCallback != null) {
mEnrollmentCallback.onEnrollmentHelp(clientInfo, msg);
} else if (mAuthenticationCallback != null) {
- mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg);
+ if (acquireInfo != BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START) {
+ mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg);
+ }
}
}
@@ -1050,6 +1052,9 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
return msgArray[vendorCode];
}
}
+ break;
+ case FINGERPRINT_ACQUIRED_START:
+ return null;
}
Slog.w(TAG, "Invalid acquired message: " + acquireInfo + ", " + vendorCode);
return null;
diff --git a/core/java/android/os/incremental/IncrementalFileStorages.java b/core/java/android/os/incremental/IncrementalFileStorages.java
index 987a53e337a0..25fb3e0ed907 100644
--- a/core/java/android/os/incremental/IncrementalFileStorages.java
+++ b/core/java/android/os/incremental/IncrementalFileStorages.java
@@ -29,6 +29,8 @@ package android.os.incremental;
* @throws IllegalStateException the session is not an Incremental installation session.
*/
+import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -85,7 +87,7 @@ public final class IncrementalFileStorages {
try {
result = new IncrementalFileStorages(stageDir, incrementalManager, dataLoaderParams);
for (InstallationFile file : addedFiles) {
- if (file.getFileType() == InstallationFile.FILE_TYPE_APK) {
+ if (file.getLocation() == LOCATION_DATA_APP) {
try {
result.addApkFile(file);
} catch (IOException e) {
@@ -95,7 +97,7 @@ public final class IncrementalFileStorages {
e);
}
} else {
- throw new IOException("Unknown file type: " + file.getFileType());
+ throw new IOException("Unknown file location: " + file.getLocation());
}
}
@@ -147,8 +149,8 @@ public final class IncrementalFileStorages {
String apkName = apk.getName();
File targetFile = Paths.get(stageDirPath, apkName).toFile();
if (!targetFile.exists()) {
- mDefaultStorage.makeFile(apkName, apk.getSize(), null,
- apk.getMetadata(), 0, null, null, null);
+ mDefaultStorage.makeFile(apkName, apk.getLengthBytes(), null, apk.getMetadata(),
+ apk.getSignature());
}
}
diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java
index ba38268949b5..d2d8f85b1b35 100644
--- a/core/java/android/os/incremental/IncrementalManager.java
+++ b/core/java/android/os/incremental/IncrementalManager.java
@@ -299,7 +299,16 @@ public final class IncrementalManager {
return nativeIsIncrementalPath(path);
}
+ /**
+ * Returns raw signature for file if it's on Incremental File System.
+ * Unsafe, use only if you are sure what you are doing.
+ */
+ public static @Nullable byte[] unsafeGetFileSignature(@NonNull String path) {
+ return nativeUnsafeGetFileSignature(path);
+ }
+
/* Native methods */
private static native boolean nativeIsEnabled();
private static native boolean nativeIsIncrementalPath(@NonNull String path);
+ private static native byte[] nativeUnsafeGetFileSignature(@NonNull String path);
}
diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java
index 5df44ff49059..f4e1f967dca8 100644
--- a/core/java/android/os/incremental/IncrementalStorage.java
+++ b/core/java/android/os/incremental/IncrementalStorage.java
@@ -20,6 +20,8 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.RemoteException;
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -169,10 +171,11 @@ public final class IncrementalStorage {
* @param path Relative path of the new file.
* @param size Size of the new file in bytes.
* @param metadata Metadata bytes.
+ * @param v4signatureBytes Serialized V4SignatureProto.
*/
public void makeFile(@NonNull String path, long size, @Nullable UUID id,
- @Nullable byte[] metadata, int hashAlgorithm, @Nullable byte[] rootHash,
- @Nullable byte[] additionalData, @Nullable byte[] signature) throws IOException {
+ @Nullable byte[] metadata, @Nullable byte[] v4signatureBytes)
+ throws IOException {
try {
if (id == null && metadata == null) {
throw new IOException("File ID and metadata cannot both be null");
@@ -181,13 +184,7 @@ public final class IncrementalStorage {
params.size = size;
params.metadata = (metadata == null ? new byte[0] : metadata);
params.fileId = idToBytes(id);
- if (hashAlgorithm != 0 || signature != null) {
- params.signature = new IncrementalSignature();
- params.signature.hashAlgorithm = hashAlgorithm;
- params.signature.rootHash = rootHash;
- params.signature.additionalData = additionalData;
- params.signature.signature = signature;
- }
+ params.signature = parseV4Signature(v4signatureBytes);
int res = mService.makeFile(mId, path, params);
if (res != 0) {
throw new IOException("makeFile() failed with errno " + -res);
@@ -197,6 +194,7 @@ public final class IncrementalStorage {
}
}
+
/**
* Creates a file in Incremental storage. The content of the file is mapped from a range inside
* a source file in the same storage.
@@ -349,6 +347,37 @@ public final class IncrementalStorage {
}
}
+ /**
+ * Returns the metadata object of an IncFs File.
+ *
+ * @param id The file id.
+ * @return Byte array that contains metadata bytes.
+ */
+ @Nullable
+ public byte[] getFileMetadata(@NonNull UUID id) {
+ try {
+ final byte[] rawId = idToBytes(id);
+ return mService.getMetadataById(mId, rawId);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ return null;
+ }
+ }
+
+ /**
+ * Informs the data loader service associated with the current storage to start data loader
+ *
+ * @return True if data loader is successfully started.
+ */
+ public boolean startLoading() {
+ try {
+ return mService.startLoading(mId);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ return false;
+ }
+ }
+
private static final int UUID_BYTE_SIZE = 16;
/**
@@ -386,35 +415,44 @@ public final class IncrementalStorage {
return new UUID(msb, lsb);
}
+ private static final int INCFS_HASH_SHA256 = 1;
+ private static final int INCFS_MAX_HASH_SIZE = 32; // SHA256
+ private static final int INCFS_MAX_ADD_DATA_SIZE = 128;
+
/**
- * Returns the metadata object of an IncFs File.
- *
- * @param id The file id.
- * @return Byte array that contains metadata bytes.
+ * Deserialize and validate v4 signature bytes.
*/
- @Nullable
- public byte[] getFileMetadata(@NonNull UUID id) {
- try {
- final byte[] rawId = idToBytes(id);
- return mService.getMetadataById(mId, rawId);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ private static IncrementalSignature parseV4Signature(@Nullable byte[] v4signatureBytes)
+ throws IOException {
+ if (v4signatureBytes == null) {
return null;
}
- }
- /**
- * Informs the data loader service associated with the current storage to start data loader
- *
- * @return True if data loader is successfully started.
- */
- public boolean startLoading() {
- try {
- return mService.startLoading(mId);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- return false;
+ final V4Signature signature;
+ try (DataInputStream input = new DataInputStream(
+ new ByteArrayInputStream(v4signatureBytes))) {
+ signature = V4Signature.readFrom(input);
+ }
+
+ final byte[] rootHash = signature.verityRootHash;
+ final byte[] additionalData = signature.v3Digest;
+ final byte[] pkcs7Signature = signature.pkcs7SignatureBlock;
+
+ if (rootHash.length != INCFS_MAX_HASH_SIZE) {
+ throw new IOException("rootHash has to be " + INCFS_MAX_HASH_SIZE + " bytes");
+ }
+ if (additionalData.length > INCFS_MAX_ADD_DATA_SIZE) {
+ throw new IOException(
+ "additionalData has to be at most " + INCFS_MAX_ADD_DATA_SIZE + " bytes");
}
+
+ IncrementalSignature result = new IncrementalSignature();
+ result.hashAlgorithm = INCFS_HASH_SHA256;
+ result.rootHash = rootHash;
+ result.additionalData = additionalData;
+ result.signature = pkcs7Signature;
+
+ return result;
}
/**
diff --git a/core/java/android/os/incremental/V4Signature.java b/core/java/android/os/incremental/V4Signature.java
new file mode 100644
index 000000000000..5fadee4355b3
--- /dev/null
+++ b/core/java/android/os/incremental/V4Signature.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.incremental;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+/**
+ * V4 signature fields.
+ * Keep in sync with APKSig master copy.
+ * @hide
+ */
+public class V4Signature {
+ public final byte[] verityRootHash;
+ public final byte[] v3Digest;
+ public final byte[] pkcs7SignatureBlock;
+
+ V4Signature(byte[] verityRootHash, byte[] v3Digest, byte[] pkcs7SignatureBlock) {
+ this.verityRootHash = verityRootHash;
+ this.v3Digest = v3Digest;
+ this.pkcs7SignatureBlock = pkcs7SignatureBlock;
+ }
+
+ static byte[] readBytes(DataInputStream stream) throws IOException {
+ byte[] result = new byte[stream.readInt()];
+ stream.read(result);
+ return result;
+ }
+
+ static V4Signature readFrom(DataInputStream stream) throws IOException {
+ byte[] verityRootHash = readBytes(stream);
+ byte[] v3Digest = readBytes(stream);
+ byte[] pkcs7SignatureBlock = readBytes(stream);
+ return new V4Signature(verityRootHash, v3Digest, pkcs7SignatureBlock);
+ }
+
+ static void writeBytes(DataOutputStream stream, byte[] bytes) throws IOException {
+ stream.writeInt(bytes.length);
+ stream.write(bytes);
+ }
+
+ void writeTo(DataOutputStream stream) throws IOException {
+ writeBytes(stream, this.verityRootHash);
+ writeBytes(stream, this.v3Digest);
+ writeBytes(stream, this.pkcs7SignatureBlock);
+ }
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index b9207e5a12d5..ea0afa94c8aa 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10207,7 +10207,9 @@ public final class Settings {
*
* Type: int (0 for false, 1 for true)
* @hide
+ * @deprecated Use {@link WifiManager#isAutoWakeupEnabled()} instead.
*/
+ @Deprecated
@SystemApi
public static final String WIFI_WAKEUP_ENABLED = "wifi_wakeup_enabled";
@@ -10243,7 +10245,6 @@ public final class Settings {
* enabled state.
* @hide
*/
- @SystemApi
public static final String NETWORK_RECOMMENDATIONS_ENABLED =
"network_recommendations_enabled";
diff --git a/core/java/android/service/dataloader/DataLoaderService.java b/core/java/android/service/dataloader/DataLoaderService.java
index c21577842199..b9700b2b80db 100644
--- a/core/java/android/service/dataloader/DataLoaderService.java
+++ b/core/java/android/service/dataloader/DataLoaderService.java
@@ -90,7 +90,7 @@ public abstract class DataLoaderService extends Service {
* @hide
*/
@SystemApi
- public @Nullable DataLoader onCreateDataLoader() {
+ public @Nullable DataLoader onCreateDataLoader(@NonNull DataLoaderParams dataLoaderParams) {
return null;
}
diff --git a/core/java/android/view/AccessibilityEmbeddedConnection.java b/core/java/android/view/AccessibilityEmbeddedConnection.java
new file mode 100644
index 000000000000..cc1e5010edc7
--- /dev/null
+++ b/core/java/android/view/AccessibilityEmbeddedConnection.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Matrix;
+import android.os.IBinder;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.IAccessibilityEmbeddedConnection;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * This class is an interface this ViewRootImpl provides to the host view to the latter
+ * can interact with the view hierarchy in SurfaceControlViewHost.
+ *
+ * @hide
+ */
+final class AccessibilityEmbeddedConnection extends IAccessibilityEmbeddedConnection.Stub {
+ private final WeakReference<ViewRootImpl> mViewRootImpl;
+
+ AccessibilityEmbeddedConnection(ViewRootImpl viewRootImpl) {
+ mViewRootImpl = new WeakReference<>(viewRootImpl);
+ }
+
+ @Override
+ public @Nullable IBinder associateEmbeddedHierarchy(@NonNull IBinder host, int hostViewId) {
+ final ViewRootImpl viewRootImpl = mViewRootImpl.get();
+ if (viewRootImpl != null) {
+ final AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(
+ viewRootImpl.mContext);
+ viewRootImpl.mAttachInfo.mLeashedParentToken = host;
+ viewRootImpl.mAttachInfo.mLeashedParentAccessibilityViewId = hostViewId;
+ if (accessibilityManager.isEnabled()) {
+ accessibilityManager.associateEmbeddedHierarchy(host, viewRootImpl.mLeashToken);
+ }
+ return viewRootImpl.mLeashToken;
+ }
+ return null;
+ }
+
+ @Override
+ public void disassociateEmbeddedHierarchy() {
+ final ViewRootImpl viewRootImpl = mViewRootImpl.get();
+ if (viewRootImpl != null) {
+ final AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(
+ viewRootImpl.mContext);
+ viewRootImpl.mAttachInfo.mLeashedParentToken = null;
+ viewRootImpl.mAttachInfo.mLeashedParentAccessibilityViewId = View.NO_ID;
+ viewRootImpl.mAttachInfo.mLocationInParentDisplay.set(0, 0);
+ if (accessibilityManager.isEnabled()) {
+ accessibilityManager.disassociateEmbeddedHierarchy(viewRootImpl.mLeashToken);
+ }
+ }
+ }
+
+ @Override
+ public void setScreenMatrix(float[] matrixValues) {
+ final ViewRootImpl viewRootImpl = mViewRootImpl.get();
+ if (viewRootImpl != null) {
+ // TODO(b/148821260): Implement the rest of matrix values.
+ viewRootImpl.mAttachInfo.mLocationInParentDisplay.set(
+ (int) matrixValues[Matrix.MTRANS_X], (int) matrixValues[Matrix.MTRANS_Y]);
+ }
+ }
+}
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index 203b08765663..3ca84c1eb18d 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -855,6 +855,36 @@ public final class AccessibilityInteractionController {
return mViewRootImpl.mAttachInfo.mLocationInParentDisplay.equals(0, 0);
}
+ private void associateLeashedParentIfNeeded(List<AccessibilityNodeInfo> infos) {
+ if (infos == null || shouldBypassAssociateLeashedParent()) {
+ return;
+ }
+ final int infoCount = infos.size();
+ for (int i = 0; i < infoCount; i++) {
+ final AccessibilityNodeInfo info = infos.get(i);
+ associateLeashedParentIfNeeded(info);
+ }
+ }
+
+ private void associateLeashedParentIfNeeded(AccessibilityNodeInfo info) {
+ if (info == null || shouldBypassAssociateLeashedParent()) {
+ return;
+ }
+ // The node id of root node in embedded maybe not be ROOT_NODE_ID so we compare the id
+ // with root view.
+ if (mViewRootImpl.mView.getAccessibilityViewId()
+ != AccessibilityNodeInfo.getAccessibilityViewId(info.getSourceNodeId())) {
+ return;
+ }
+ info.setLeashedParent(mViewRootImpl.mAttachInfo.mLeashedParentToken,
+ mViewRootImpl.mAttachInfo.mLeashedParentAccessibilityViewId);
+ }
+
+ private boolean shouldBypassAssociateLeashedParent() {
+ return (mViewRootImpl.mAttachInfo.mLeashedParentToken == null
+ && mViewRootImpl.mAttachInfo.mLeashedParentAccessibilityViewId == View.NO_ID);
+ }
+
private void applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info,
MagnificationSpec spec) {
if (info == null) {
@@ -914,6 +944,7 @@ public final class AccessibilityInteractionController {
MagnificationSpec spec, Region interactiveRegion) {
try {
mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+ associateLeashedParentIfNeeded(infos);
adjustBoundsInScreenIfNeeded(infos);
// To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node,
// then impact the visibility result, we need to adjust visibility before apply scale.
@@ -935,6 +966,7 @@ public final class AccessibilityInteractionController {
MagnificationSpec spec, Region interactiveRegion) {
try {
mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+ associateLeashedParentIfNeeded(info);
adjustBoundsInScreenIfNeeded(info);
// To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node,
// then impact the visibility result, we need to adjust visibility before apply scale.
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index f9a023fafc3c..17303478fa50 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -97,6 +97,8 @@ interface IWindowManager
IWindowSession openSession(in IWindowSessionCallback callback);
+ boolean useBLAST();
+
@UnsupportedAppUsage
void getInitialDisplaySize(int displayId, out Point size);
@UnsupportedAppUsage
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index d4961eab7473..6caa4fed6409 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -198,7 +198,7 @@ public class InsetsSource implements Parcelable {
return "InsetsSource: {"
+ "mType=" + InsetsState.typeToString(mType)
+ ", mFrame=" + mFrame.toShortString()
- + ", mVisible" + mVisible
+ + ", mVisible=" + mVisible
+ "}";
}
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 78a080de20e5..4ac6a666a21b 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -258,14 +258,14 @@ public class Surface implements Parcelable {
*/
public void release() {
synchronized (mLock) {
- if (mNativeObject != 0) {
- nativeRelease(mNativeObject);
- setNativeObjectLocked(0);
- }
if (mHwuiContext != null) {
mHwuiContext.destroy();
mHwuiContext = null;
}
+ if (mNativeObject != 0) {
+ nativeRelease(mNativeObject);
+ setNativeObjectLocked(0);
+ }
}
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index b1c354f6f717..637a088e4c5d 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -22,7 +22,6 @@ import static android.view.WindowManagerPolicyConstants.APPLICATION_PANEL_SUBLAY
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.CompatibilityInfo.Translator;
@@ -43,8 +42,8 @@ import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceControl.Transaction;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.view.SurfaceControlViewHost;
+import android.view.accessibility.AccessibilityNodeInfo;
import com.android.internal.view.SurfaceCallbackHelper;
@@ -386,7 +385,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
* This gets called on a RenderThread worker thread, so members accessed here must
* be protected by a lock.
*/
- final boolean useBLAST = WindowManagerGlobal.USE_BLAST_ADAPTER;
+ final boolean useBLAST = WindowManagerGlobal.getInstance().useBLAST();
viewRoot.registerRtFrameCallback(frame -> {
try {
final SurfaceControl.Transaction t = useBLAST ?
@@ -1120,7 +1119,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
private void applySurfaceTransforms(SurfaceControl surface, SurfaceControl.Transaction t,
Rect position, long frameNumber) {
- if (frameNumber > 0 && !WindowManagerGlobal.USE_BLAST_ADAPTER) {
+ if (frameNumber > 0 && !WindowManagerGlobal.getInstance().useBLAST()) {
final ViewRootImpl viewRoot = getViewRootImpl();
t.deferTransactionUntil(surface, viewRoot.getRenderSurfaceControl(),
@@ -1138,7 +1137,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
}
private void setParentSpaceRectangle(Rect position, long frameNumber) {
- final boolean useBLAST = WindowManagerGlobal.USE_BLAST_ADAPTER;
+ final boolean useBLAST = WindowManagerGlobal.getInstance().useBLAST();
final ViewRootImpl viewRoot = getViewRootImpl();
final SurfaceControl.Transaction t = useBLAST ? viewRoot.getBLASTSyncTransaction() :
mRtTransaction;
@@ -1199,7 +1198,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
@Override
public void positionLost(long frameNumber) {
- boolean useBLAST = WindowManagerGlobal.USE_BLAST_ADAPTER;
+ boolean useBLAST = WindowManagerGlobal.getInstance().useBLAST();
if (DEBUG) {
Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d",
System.identityHashCode(this), frameNumber));
@@ -1538,7 +1537,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
@Override
public void invalidate(boolean invalidateCache) {
super.invalidate(invalidateCache);
- if (!WindowManagerGlobal.USE_BLAST_ADAPTER) {
+ if (!WindowManagerGlobal.getInstance().useBLAST()) {
return;
}
final ViewRootImpl viewRoot = getViewRootImpl();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index a6f8fad817e1..f99c96584585 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -28989,6 +28989,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
OnContentApplyWindowInsetsListener mContentOnApplyWindowInsetsListener;
/**
+ * The leash token of this view's parent when it's in an embedded hierarchy that is
+ * re-parented to another window.
+ */
+ IBinder mLeashedParentToken;
+
+ /**
+ * The accessibility view id of this view's parent when it's in an embedded
+ * hierarchy that is re-parented to another window.
+ */
+ int mLeashedParentAccessibilityViewId;
+
+ /**
* Creates a new set of attachment information with the specified
* events handler and thread.
*
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 159b93eb12dd..fa4fafaf3b26 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -135,6 +135,7 @@ import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.accessibility.AccessibilityNodeProvider;
import android.view.accessibility.AccessibilityWindowInfo;
+import android.view.accessibility.IAccessibilityEmbeddedConnection;
import android.view.accessibility.IAccessibilityInteractionConnection;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
import android.view.animation.AccelerateDecelerateInterpolator;
@@ -315,6 +316,8 @@ public final class ViewRootImpl implements ViewParent,
*/
private boolean mForceNextConfigUpdate;
+ private final boolean mUseBLASTAdapter;
+
/**
* Signals that compatibility booleans have been initialized according to
* target SDK versions.
@@ -353,6 +356,8 @@ public final class ViewRootImpl implements ViewParent,
final W mWindow;
+ final IBinder mLeashToken;
+
final int mTargetSdkVersion;
int mSeq;
@@ -650,6 +655,8 @@ public final class ViewRootImpl implements ViewParent,
private final GestureExclusionTracker mGestureExclusionTracker = new GestureExclusionTracker();
+ private IAccessibilityEmbeddedConnection mEmbeddedConnection;
+
static final class SystemUiVisibilityInfo {
int seq;
int globalVisibility;
@@ -683,6 +690,7 @@ public final class ViewRootImpl implements ViewParent,
mVisRect = new Rect();
mWinFrame = new Rect();
mWindow = new W(this);
+ mLeashToken = new Binder();
mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
mViewVisibility = View.GONE;
mTransparentRegion = new Region();
@@ -734,6 +742,7 @@ public final class ViewRootImpl implements ViewParent,
loadSystemProperties();
mImeFocusController = new ImeFocusController(this);
+ mUseBLASTAdapter = WindowManagerGlobal.getInstance().useBLAST();
}
public static void addFirstDrawHandler(Runnable callback) {
@@ -861,7 +870,7 @@ public final class ViewRootImpl implements ViewParent,
if (mWindowAttributes.packageName == null) {
mWindowAttributes.packageName = mBasePackageName;
}
- if (WindowManagerGlobal.USE_BLAST_ADAPTER) {
+ if (mUseBLASTAdapter) {
mWindowAttributes.privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
}
@@ -1341,7 +1350,7 @@ public final class ViewRootImpl implements ViewParent,
}
mWindowAttributes.privateFlags |= compatibleWindowFlag;
- if (WindowManagerGlobal.USE_BLAST_ADAPTER) {
+ if (mUseBLASTAdapter) {
mWindowAttributes.privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
}
@@ -7342,7 +7351,7 @@ public final class ViewRootImpl implements ViewParent,
mPendingMergedConfiguration, mSurfaceControl, mTempInsets, mSurfaceSize,
mBlastSurfaceControl);
if (mSurfaceControl.isValid()) {
- if (!WindowManagerGlobal.USE_BLAST_ADAPTER) {
+ if (!mUseBLASTAdapter) {
mSurface.copyFrom(mSurfaceControl);
} else {
mSurface.transferFrom(getOrCreateBLASTSurface(mSurfaceSize.x,
@@ -9154,6 +9163,10 @@ public final class ViewRootImpl implements ViewParent,
focusedView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
}
}
+ if (mAttachInfo.mLeashedParentToken != null) {
+ mAccessibilityManager.associateEmbeddedHierarchy(
+ mAttachInfo.mLeashedParentToken, mLeashToken);
+ }
} else {
ensureNoConnection();
mHandler.obtainMessage(MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST).sendToTarget();
@@ -9166,6 +9179,7 @@ public final class ViewRootImpl implements ViewParent,
if (!registered) {
mAttachInfo.mAccessibilityWindowId =
mAccessibilityManager.addAccessibilityInteractionConnection(mWindow,
+ mLeashToken,
mContext.getPackageName(),
new AccessibilityInteractionConnection(ViewRootImpl.this));
}
@@ -9352,6 +9366,17 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ /**
+ * Gets an accessibility embedded connection interface for this ViewRootImpl.
+ * @hide
+ */
+ public IAccessibilityEmbeddedConnection getEmbeddedConnection() {
+ if (mEmbeddedConnection == null) {
+ mEmbeddedConnection = new AccessibilityEmbeddedConnection(ViewRootImpl.this);
+ }
+ return mEmbeddedConnection;
+ }
+
private class SendWindowContentChangedAccessibilityEvent implements Runnable {
private int mChangeTypes = 0;
@@ -9537,7 +9562,7 @@ public final class ViewRootImpl implements ViewParent,
}
SurfaceControl getRenderSurfaceControl() {
- if (WindowManagerGlobal.USE_BLAST_ADAPTER) {
+ if (mUseBLASTAdapter) {
return mBlastSurfaceControl;
} else {
return mSurfaceControl;
diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index f501de91b3b7..ea8e73885980 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -16,8 +16,6 @@
package android.view;
-import static android.view.WindowInsets.Type.ime;
-
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -148,7 +146,7 @@ public interface WindowInsetsController {
* @param types The {@link InsetsType}s the application has requested to control.
* @param durationMillis Duration of animation in
* {@link java.util.concurrent.TimeUnit#MILLISECONDS}, or -1 if the
- * animation doesn't have a predetermined duration.T his value will be
+ * animation doesn't have a predetermined duration. This value will be
* passed to {@link InsetsAnimation#getDurationMillis()}
* @param interpolator The interpolator used for this animation, or {@code null} if this
* animation doesn't follow an interpolation curve. This value will be
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index f03c4e731283..c22b8921390c 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -56,13 +56,7 @@ import java.util.ArrayList;
public final class WindowManagerGlobal {
private static final String TAG = "WindowManager";
- private static final String WM_USE_BLAST_ADAPTER_FLAG = "wm_use_blast_adapter";
-
- /**
- * This flag controls whether ViewRootImpl will utilize the Blast Adapter
- * to send buffer updates to SurfaceFlinger
- */
- public static final boolean USE_BLAST_ADAPTER = false;
+ private final boolean mUseBLASTAdapter;
/**
* The user is navigating with keys (not the touch screen), so
@@ -165,6 +159,11 @@ public final class WindowManagerGlobal {
private Runnable mSystemPropertyUpdater;
private WindowManagerGlobal() {
+ try {
+ mUseBLASTAdapter = getWindowManagerService().useBLAST();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
@UnsupportedAppUsage
@@ -233,6 +232,13 @@ public final class WindowManagerGlobal {
}
}
+ /**
+ * Whether or not to use BLAST for ViewRootImpl
+ */
+ public boolean useBLAST() {
+ return mUseBLASTAdapter;
+ }
+
@UnsupportedAppUsage
public String[] getViewRootNames() {
synchronized (mLock) {
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 02b098b27974..dc87453bd867 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -1086,6 +1086,50 @@ public final class AccessibilityManager {
}
/**
+ * Associate the connection between the host View and the embedded SurfaceControlViewHost.
+ *
+ * @hide
+ */
+ public void associateEmbeddedHierarchy(@NonNull IBinder host, @NonNull IBinder embedded) {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ }
+ try {
+ service.associateEmbeddedHierarchy(host, embedded);
+ } catch (RemoteException e) {
+ return;
+ }
+ }
+
+ /**
+ * Disassociate the connection between the host View and the embedded SurfaceControlViewHost.
+ * The given token could be either from host side or embedded side.
+ *
+ * @hide
+ */
+ public void disassociateEmbeddedHierarchy(@NonNull IBinder token) {
+ if (token == null) {
+ return;
+ }
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ }
+ try {
+ service.disassociateEmbeddedHierarchy(token);
+ } catch (RemoteException e) {
+ return;
+ }
+ }
+
+ /**
* Sets the current state and notifies listeners, if necessary.
*
* @param stateFlags The state flags.
@@ -1147,11 +1191,12 @@ public final class AccessibilityManager {
/**
* Adds an accessibility interaction connection interface for a given window.
* @param windowToken The window token to which a connection is added.
+ * @param leashToken The leash token to which a connection is added.
* @param connection The connection.
*
* @hide
*/
- public int addAccessibilityInteractionConnection(IWindow windowToken,
+ public int addAccessibilityInteractionConnection(IWindow windowToken, IBinder leashToken,
String packageName, IAccessibilityInteractionConnection connection) {
final IAccessibilityManager service;
final int userId;
@@ -1163,8 +1208,8 @@ public final class AccessibilityManager {
userId = mUserId;
}
try {
- return service.addAccessibilityInteractionConnection(windowToken, connection,
- packageName, userId);
+ return service.addAccessibilityInteractionConnection(windowToken, leashToken,
+ connection, packageName, userId);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re);
}
diff --git a/core/java/android/view/accessibility/IAccessibilityEmbeddedConnection.aidl b/core/java/android/view/accessibility/IAccessibilityEmbeddedConnection.aidl
new file mode 100644
index 000000000000..707099ef09f4
--- /dev/null
+++ b/core/java/android/view/accessibility/IAccessibilityEmbeddedConnection.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.accessibility;
+
+/**
+ * Interface used by host View to talk to the view root of the embedded SurfaceControlViewHost
+ * that actually implements the functionality.
+ *
+ * @hide
+ */
+interface IAccessibilityEmbeddedConnection {
+
+ IBinder associateEmbeddedHierarchy(IBinder hostToken, int sourceId);
+
+ void disassociateEmbeddedHierarchy();
+
+ oneway void setScreenMatrix(in float[] matrixValues);
+}
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 7f8fdf83995a..97036f3f3a1b 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -47,7 +47,7 @@ interface IAccessibilityManager {
@UnsupportedAppUsage
List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType, int userId);
- int addAccessibilityInteractionConnection(IWindow windowToken,
+ int addAccessibilityInteractionConnection(IWindow windowToken, IBinder leashToken,
in IAccessibilityInteractionConnection connection,
String packageName, int userId);
@@ -88,4 +88,8 @@ interface IAccessibilityManager {
oneway void registerSystemAction(in RemoteAction action, int actionId);
oneway void unregisterSystemAction(int actionId);
oneway void setWindowMagnificationConnection(in IWindowMagnificationConnection connection);
+
+ void associateEmbeddedHierarchy(IBinder host, IBinder embedded);
+
+ void disassociateEmbeddedHierarchy(IBinder token);
}
diff --git a/core/java/android/view/textclassifier/TextClassificationSession.java b/core/java/android/view/textclassifier/TextClassificationSession.java
index 4329a206746d..fed3dbf8f49c 100644
--- a/core/java/android/view/textclassifier/TextClassificationSession.java
+++ b/core/java/android/view/textclassifier/TextClassificationSession.java
@@ -16,6 +16,7 @@
package android.view.textclassifier;
+import android.annotation.NonNull;
import android.annotation.WorkerThread;
import android.view.textclassifier.SelectionEvent.InvocationMethod;
@@ -23,6 +24,8 @@ import com.android.internal.util.Preconditions;
import java.util.Objects;
+import sun.misc.Cleaner;
+
/**
* Session-aware TextClassifier.
*/
@@ -35,6 +38,7 @@ final class TextClassificationSession implements TextClassifier {
private final SelectionEventHelper mEventHelper;
private final TextClassificationSessionId mSessionId;
private final TextClassificationContext mClassificationContext;
+ private final Cleaner mCleaner;
private boolean mDestroyed;
@@ -44,6 +48,8 @@ final class TextClassificationSession implements TextClassifier {
mSessionId = new TextClassificationSessionId();
mEventHelper = new SelectionEventHelper(mSessionId, mClassificationContext);
initializeRemoteSession();
+ // This ensures destroy() is called if the client forgot to do so.
+ mCleaner = Cleaner.create(this, new CleanerRunnable(mEventHelper, mDelegate));
}
@Override
@@ -114,8 +120,7 @@ final class TextClassificationSession implements TextClassifier {
@Override
public void destroy() {
- mEventHelper.endSession();
- mDelegate.destroy();
+ mCleaner.clean();
mDestroyed = true;
}
@@ -258,4 +263,25 @@ final class TextClassificationSession implements TextClassifier {
}
}
}
+
+ // We use a static nested class here to avoid retaining the object reference of the outer
+ // class. Otherwise. the Cleaner would never be triggered.
+ private static class CleanerRunnable implements Runnable {
+ @NonNull
+ private final SelectionEventHelper mEventHelper;
+ @NonNull
+ private final TextClassifier mDelegate;
+
+ CleanerRunnable(
+ @NonNull SelectionEventHelper eventHelper, @NonNull TextClassifier delegate) {
+ mEventHelper = Objects.requireNonNull(eventHelper);
+ mDelegate = Objects.requireNonNull(delegate);
+ }
+
+ @Override
+ public void run() {
+ mEventHelper.endSession();
+ mDelegate.destroy();
+ }
+ }
}
diff --git a/core/java/android/view/textclassifier/TextClassificationSessionId.java b/core/java/android/view/textclassifier/TextClassificationSessionId.java
index f90e6b2e407c..0b6fba225280 100644
--- a/core/java/android/view/textclassifier/TextClassificationSessionId.java
+++ b/core/java/android/view/textclassifier/TextClassificationSessionId.java
@@ -17,6 +17,8 @@
package android.view.textclassifier;
import android.annotation.NonNull;
+import android.os.Binder;
+import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
@@ -28,7 +30,10 @@ import java.util.UUID;
* This class represents the id of a text classification session.
*/
public final class TextClassificationSessionId implements Parcelable {
- private final @NonNull String mValue;
+ @NonNull
+ private final String mValue;
+ @NonNull
+ private final IBinder mToken;
/**
* Creates a new instance.
@@ -36,7 +41,7 @@ public final class TextClassificationSessionId implements Parcelable {
* @hide
*/
public TextClassificationSessionId() {
- this(UUID.randomUUID().toString());
+ this(UUID.randomUUID().toString(), new Binder());
}
/**
@@ -46,34 +51,28 @@ public final class TextClassificationSessionId implements Parcelable {
*
* @hide
*/
- public TextClassificationSessionId(@NonNull String value) {
- mValue = value;
+ public TextClassificationSessionId(@NonNull String value, @NonNull IBinder token) {
+ mValue = Objects.requireNonNull(value);
+ mToken = Objects.requireNonNull(token);
+ }
+
+ /** @hide */
+ @NonNull
+ public IBinder getToken() {
+ return mToken;
}
@Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + mValue.hashCode();
- return result;
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ TextClassificationSessionId that = (TextClassificationSessionId) o;
+ return Objects.equals(mValue, that.mValue) && Objects.equals(mToken, that.mToken);
}
@Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (getClass() != obj.getClass()) {
- return false;
- }
- TextClassificationSessionId other = (TextClassificationSessionId) obj;
- if (!mValue.equals(other.mValue)) {
- return false;
- }
- return true;
+ public int hashCode() {
+ return Objects.hash(mValue, mToken);
}
@Override
@@ -84,6 +83,7 @@ public final class TextClassificationSessionId implements Parcelable {
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeString(mValue);
+ parcel.writeStrongBinder(mToken);
}
@Override
@@ -96,28 +96,18 @@ public final class TextClassificationSessionId implements Parcelable {
*
* @return The flattened id.
*/
- public @NonNull String flattenToString() {
+ @NonNull
+ public String flattenToString() {
return mValue;
}
- /**
- * Unflattens a print job id from a string.
- *
- * @param string The string.
- * @return The unflattened id, or null if the string is malformed.
- *
- * @hide
- */
- public static @NonNull TextClassificationSessionId unflattenFromString(@NonNull String string) {
- return new TextClassificationSessionId(string);
- }
-
- public static final @android.annotation.NonNull Parcelable.Creator<TextClassificationSessionId> CREATOR =
+ @NonNull
+ public static final Parcelable.Creator<TextClassificationSessionId> CREATOR =
new Parcelable.Creator<TextClassificationSessionId>() {
@Override
public TextClassificationSessionId createFromParcel(Parcel parcel) {
return new TextClassificationSessionId(
- Objects.requireNonNull(parcel.readString()));
+ parcel.readString(), parcel.readStrongBinder());
}
@Override
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 3322834c9ed1..086b9d83fed5 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -377,6 +377,11 @@ public final class SystemUiDeviceConfigFlags {
public static final String NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN =
"nav_bar_handle_show_over_lockscreen";
+ /**
+ * (boolean) Whether to enable user-drag resizing for PIP.
+ */
+ public static final String PIP_USER_RESIZE = "pip_user_resize";
+
private SystemUiDeviceConfigFlags() {
}
}
diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp
index 571a33879411..c49c3a0f6b61 100644
--- a/core/jni/LayoutlibLoader.cpp
+++ b/core/jni/LayoutlibLoader.cpp
@@ -40,6 +40,7 @@ extern int register_android_graphics_ByteBufferStreamAdaptor(JNIEnv* env);
extern int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env);
extern int register_android_graphics_Graphics(JNIEnv* env);
extern int register_android_graphics_ImageDecoder(JNIEnv*);
+extern int register_android_graphics_Interpolator(JNIEnv* env);
extern int register_android_graphics_MaskFilter(JNIEnv* env);
extern int register_android_graphics_NinePatch(JNIEnv*);
extern int register_android_graphics_PathEffect(JNIEnv* env);
@@ -115,6 +116,7 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = {
{"android.graphics.FontFamily", REG_JNI(register_android_graphics_FontFamily)},
{"android.graphics.Graphics", REG_JNI(register_android_graphics_Graphics)},
{"android.graphics.ImageDecoder", REG_JNI(register_android_graphics_ImageDecoder)},
+ {"android.graphics.Interpolator", REG_JNI(register_android_graphics_Interpolator)},
{"android.graphics.MaskFilter", REG_JNI(register_android_graphics_MaskFilter)},
{"android.graphics.Matrix", REG_JNI(register_android_graphics_Matrix)},
{"android.graphics.NinePatch", REG_JNI(register_android_graphics_NinePatch)},
diff --git a/core/jni/android_os_incremental_IncrementalManager.cpp b/core/jni/android_os_incremental_IncrementalManager.cpp
index d41e98241b07..44bff0188544 100644
--- a/core/jni/android_os_incremental_IncrementalManager.cpp
+++ b/core/jni/android_os_incremental_IncrementalManager.cpp
@@ -37,10 +37,28 @@ static jboolean nativeIsIncrementalPath(JNIEnv* env,
return (jboolean)IncFs_IsIncFsPath(path.c_str());
}
-static const JNINativeMethod method_table[] = {
- {"nativeIsEnabled", "()Z", (void*)nativeIsEnabled},
- {"nativeIsIncrementalPath", "(Ljava/lang/String;)Z", (void*)nativeIsIncrementalPath},
-};
+static jbyteArray nativeUnsafeGetFileSignature(JNIEnv* env, jobject clazz, jstring javaPath) {
+ ScopedUtfChars path(env, javaPath);
+
+ char signature[INCFS_MAX_SIGNATURE_SIZE];
+ size_t size = sizeof(signature);
+ if (IncFs_UnsafeGetSignatureByPath(path.c_str(), signature, &size) < 0) {
+ return nullptr;
+ }
+
+ jbyteArray result = env->NewByteArray(size);
+ if (result != nullptr) {
+ env->SetByteArrayRegion(result, 0, size, (const jbyte*)signature);
+ }
+ return result;
+}
+
+static const JNINativeMethod method_table[] = {{"nativeIsEnabled", "()Z", (void*)nativeIsEnabled},
+ {"nativeIsIncrementalPath", "(Ljava/lang/String;)Z",
+ (void*)nativeIsIncrementalPath},
+ {"nativeUnsafeGetFileSignature",
+ "(Ljava/lang/String;)[B",
+ (void*)nativeUnsafeGetFileSignature}};
int register_android_os_incremental_IncrementalManager(JNIEnv* env) {
return jniRegisterNativeMethods(env, "android/os/incremental/IncrementalManager",
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 92941b8cb22b..7290e3031d21 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -611,6 +611,12 @@ static void PreApplicationInit() {
// Set the jemalloc decay time to 1.
mallopt(M_DECAY_TIME, 1);
+
+ // Maybe initialize GWP-ASan here. Must be called after
+ // mallopt(M_SET_ZYGOTE_CHILD).
+ bool ForceEnableGwpAsan = false;
+ android_mallopt(M_INITIALIZE_GWP_ASAN, &ForceEnableGwpAsan,
+ sizeof(ForceEnableGwpAsan));
}
static void SetUpSeccompFilter(uid_t uid, bool is_child_zygote) {
diff --git a/core/proto/android/content/locusid.proto b/core/proto/android/content/locusid.proto
new file mode 100644
index 000000000000..4f0ce6b19e70
--- /dev/null
+++ b/core/proto/android/content/locusid.proto
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package android.content;
+
+option java_multiple_files = true;
+
+// On disk representation of android.content.LocusId. Currently used by
+// com.android.server.people.ConversationInfoProto.
+message LocusIdProto {
+ optional string locus_id = 1;
+}
diff --git a/core/proto/android/server/peopleservice.proto b/core/proto/android/server/peopleservice.proto
new file mode 100644
index 000000000000..294b6efd3cbe
--- /dev/null
+++ b/core/proto/android/server/peopleservice.proto
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package com.android.server.people;
+
+option java_multiple_files = true;
+
+import "frameworks/base/core/proto/android/content/locusid.proto";
+
+// On disk data of conversation infos for a user and app package.
+message ConversationInfosProto {
+
+ // The series of conversation infos for a user and app package.
+ repeated ConversationInfoProto conversation_infos = 1;
+}
+
+// Individual conversation info (com.android.server.people.data.ConversationInfo) for a user
+// and app package.
+message ConversationInfoProto {
+
+ // The conversation's shortcut id.
+ optional string shortcut_id = 1;
+
+ // The conversation's locus id.
+ optional .android.content.LocusIdProto locus_id_proto = 2;
+
+ // The URI of the contact in the conversation.
+ optional string contact_uri = 3;
+
+ // The notification channel id of the conversation.
+ optional string notification_channel_id = 4;
+
+ // Integer representation of shortcut bit flags.
+ optional int32 shortcut_flags = 5;
+
+ // Integer representation of conversation bit flags.
+ optional int32 conversation_flags = 6;
+}
+
+// Individual event (com.android.server.people.data.Event).
+message PeopleEventProto {
+
+ // For valid values, refer to java class documentation.
+ optional int32 event_type = 1;
+
+ optional int64 time = 2;
+
+ // The duration of the event. Should only be set for some event_types. Refer to java class
+ // documentation for details.
+ optional int32 duration = 3;
+}
+
+// Index of events' time distributions (com.android.server.people.data.EventIndex).
+message PeopleEventIndexProto {
+ // Each long value in event_bitmaps represents a time slot, there should be 4 values. Further
+ // details can be found in class documentation.
+ repeated int64 event_bitmaps = 1;
+
+ optional int64 last_updated_time = 2;
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ff696715e94d..71a42e454f30 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -96,6 +96,7 @@
<protected-broadcast android:name="android.intent.action.OVERLAY_PRIORITY_CHANGED" />
<protected-broadcast android:name="android.intent.action.MY_PACKAGE_SUSPENDED" />
<protected-broadcast android:name="android.intent.action.MY_PACKAGE_UNSUSPENDED" />
+ <protected-broadcast android:name="android.intent.action.LOAD_DATA" />
<protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGED" />
<protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGING" />
@@ -2376,6 +2377,7 @@
@hide -->
<permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"
android:protectionLevel="signature|installer|telephony" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<!-- Allows interaction across profiles in the same profile group. -->
<permission android:name="android.permission.INTERACT_ACROSS_PROFILES"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 20901e04e6c5..7d8b8db9d4a0 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3790,6 +3790,12 @@
<attr name="description" />
<!-- Brief summary of the target of accessibility shortcut purpose or behavior. -->
<attr name="summary" />
+ <!-- Animated image of the target of accessibility shortcut purpose or behavior, to help
+ users understand how the target of accessibility shortcut can help them.-->
+ <attr name="animatedImageDrawable" format="reference"/>
+ <!-- Html description of the target of accessibility shortcut purpose or behavior, to help
+ users understand how the target of accessibility shortcut can help them. -->
+ <attr name="htmlDescription" format="string"/>
</declare-styleable>
<!-- Use <code>print-service</code> as the root tag of the XML resource that
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index f17cd45cd880..df6330654e77 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2351,7 +2351,7 @@
type. These are flags and can be freely combined.
0 - disable whitelist (install all system packages; no logging)
1 - enforce (only install system packages if they are whitelisted)
- 2 - log (log when a non-whitelisted package is run)
+ 2 - log (log non-whitelisted packages)
4 - any package not mentioned in the whitelist file is implicitly whitelisted on all users
8 - same as 4, but just for the SYSTEM user
16 - ignore OTAs (don't install system packages during OTAs)
@@ -3792,6 +3792,10 @@
or empty if the default should be used. -->
<string translatable="false" name="config_deviceSpecificAudioService"></string>
+ <!-- Class name of the device specific implementation of DisplayAreaPolicy.Provider
+ or empty if the default should be used. -->
+ <string translatable="false" name="config_deviceSpecificDisplayAreaPolicyProvider"></string>
+
<!-- Component name of media projection permission dialog -->
<string name="config_mediaProjectionPermissionDialogComponent" translatable="false">com.android.systemui/com.android.systemui.media.MediaProjectionPermissionActivity</string>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index a54566cfed17..08c840380fe5 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -414,6 +414,14 @@
logging. [CHAR LIMIT=NONE]-->
<string name="network_logging_notification_text">Your organization manages this device and may monitor network traffic. Tap for details.</string>
+ <!-- Content title for a notification. This notification indicates that the device owner has
+ changed the location settings. [CHAR LIMIT=NONE] -->
+ <string name="location_changed_notification_title">Location settings changed by your admin</string>
+ <!-- Content text for a notification. Tapping opens device location settings.
+ [CHAR LIMIT=NONE] -->
+ <string name="location_changed_notification_text">Tap to see your location settings.</string>
+
+
<!-- Factory reset warning dialog strings--> <skip />
<!-- Shows up in the dialog's title to warn about an impeding factory reset. [CHAR LIMIT=NONE] -->
<string name="factory_reset_warning">Your device will be erased</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 2dd62c176e98..0babe48e832e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1213,6 +1213,8 @@
<java-symbol type="string" name="device_ownership_relinquished" />
<java-symbol type="string" name="network_logging_notification_title" />
<java-symbol type="string" name="network_logging_notification_text" />
+ <java-symbol type="string" name="location_changed_notification_title" />
+ <java-symbol type="string" name="location_changed_notification_text" />
<java-symbol type="string" name="personal_apps_suspended_notification_title" />
<java-symbol type="string" name="personal_apps_suspended_notification_text" />
<java-symbol type="string" name="factory_reset_warning" />
@@ -3864,6 +3866,8 @@
<!-- Toast message for background started foreground service while-in-use permission restriction feature -->
<java-symbol type="string" name="allow_while_in_use_permission_in_fgs" />
+ <java-symbol type="string" name="config_deviceSpecificDisplayAreaPolicyProvider" />
+
<!-- Whether to expand the lock screen user switcher by default -->
<java-symbol type="bool" name="config_expandLockScreenUserSwitcher" />
</resources>
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 718ca46a4f18..59335a595334 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1330,12 +1330,6 @@
android:process=":FakeProvider">
</provider>
- <provider
- android:name="android.content.SlowProvider"
- android:authorities="android.content.SlowProvider"
- android:process=":SlowProvider">
- </provider>
-
<!-- Application components used for os tests -->
<service android:name="android.os.MessengerService"
diff --git a/core/tests/coretests/res/values/strings.xml b/core/tests/coretests/res/values/strings.xml
index f630188e54dc..21613a82deef 100644
--- a/core/tests/coretests/res/values/strings.xml
+++ b/core/tests/coretests/res/values/strings.xml
@@ -148,4 +148,7 @@
<!-- Summary of the accessibility shortcut [CHAR LIMIT=NONE] -->
<string name="accessibility_shortcut_summary">Accessibility shortcut summary</string>
+
+ <!-- Html description of the accessibility shortcut [CHAR LIMIT=NONE] -->
+ <string name="accessibility_shortcut_html_description">Accessibility shortcut html description</string>
</resources>
diff --git a/core/tests/coretests/res/xml/accessibility_shortcut_test_activity.xml b/core/tests/coretests/res/xml/accessibility_shortcut_test_activity.xml
index 60e29989ef0d..a597b7190fd7 100644
--- a/core/tests/coretests/res/xml/accessibility_shortcut_test_activity.xml
+++ b/core/tests/coretests/res/xml/accessibility_shortcut_test_activity.xml
@@ -19,4 +19,6 @@
<accessibility-shortcut-target xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/accessibility_shortcut_description"
android:summary="@string/accessibility_shortcut_summary"
+ android:animatedImageDrawable="@drawable/bitmap_drawable"
+ android:htmlDescription="@string/accessibility_shortcut_html_description"
/> \ No newline at end of file
diff --git a/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java b/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java
index ae6d8df26feb..82a7b2c9217e 100644
--- a/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java
+++ b/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java
@@ -84,6 +84,22 @@ public class AccessibilityShortcutInfoTest {
}
@Test
+ public void testAnimatedImageRes() {
+ assertThat("Animated image resource id is not correct",
+ mShortcutInfo.getAnimatedImageRes(), is(R.drawable.bitmap_drawable));
+ }
+
+ @Test
+ public void testHtmlDescription() {
+ final String htmlDescription = mTargetContext.getResources()
+ .getString(R.string.accessibility_shortcut_html_description);
+
+ assertNotNull("Can't find html description string", htmlDescription);
+ assertThat("Html description is not correct",
+ mShortcutInfo.loadHtmlDescription(mPackageManager), is(htmlDescription));
+ }
+
+ @Test
public void testEquals() {
assertTrue(mShortcutInfo.equals(mShortcutInfo));
assertFalse(mShortcutInfo.equals(null));
diff --git a/core/tests/coretests/src/android/content/ContentResolverTest.java b/core/tests/coretests/src/android/content/ContentResolverTest.java
index 6dc7392945d8..9dcce1e51e0b 100644
--- a/core/tests/coretests/src/android/content/ContentResolverTest.java
+++ b/core/tests/coretests/src/android/content/ContentResolverTest.java
@@ -209,13 +209,4 @@ public class ContentResolverTest {
String type = mResolver.getType(Uri.parse("content://android.content.FakeProviderRemote"));
assertEquals("fake/remote", type);
}
-
-
- @Test
- public void testGetType_slowProvider() {
- // This provider is running in a different process and is intentionally slow to start.
- // We are trying to confirm that it does not cause an ANR
- String type = mResolver.getType(Uri.parse("content://android.content.SlowProvider"));
- assertEquals("slow", type);
- }
}
diff --git a/core/tests/coretests/src/android/content/SlowProvider.java b/core/tests/coretests/src/android/content/SlowProvider.java
deleted file mode 100644
index aba32e836e80..000000000000
--- a/core/tests/coretests/src/android/content/SlowProvider.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content;
-
-import android.database.Cursor;
-import android.net.Uri;
-
-/**
- * A dummy content provider for tests. This provider runs in a different process from the test and
- * is intentionally slow.
- */
-public class SlowProvider extends ContentProvider {
-
- private static final int ON_CREATE_LATENCY_MILLIS = 3000;
-
- @Override
- public boolean onCreate() {
- try {
- Thread.sleep(ON_CREATE_LATENCY_MILLIS);
- } catch (InterruptedException e) {
- // Ignore
- }
- return true;
- }
-
- @Override
- public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
- String sortOrder) {
- return null;
- }
-
- @Override
- public String getType(Uri uri) {
- return "slow";
- }
-
- @Override
- public Uri insert(Uri uri, ContentValues values) {
- return null;
- }
-
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- return 0;
- }
-
- @Override
- public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- return 0;
- }
-}
diff --git a/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java b/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java
new file mode 100644
index 000000000000..c897ace0e0b5
--- /dev/null
+++ b/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.integrity;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableMap;
+
+import org.testng.annotations.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+public class InstallerAllowedByManifestFormulaTest {
+
+ private static final InstallerAllowedByManifestFormula
+ FORMULA = new InstallerAllowedByManifestFormula();
+
+ @Test
+ public void testFormulaMatches_installerAndCertBothInManifest() {
+ AppInstallMetadata appInstallMetadata = getAppInstallMetadataBuilder()
+ .setInstallerName("installer1")
+ .setInstallerCertificates(Arrays.asList("installer_cert1", "random_cert"))
+ .setAllowedInstallersAndCert(ImmutableMap.of(
+ "installer1", "installer_cert1",
+ "installer2", "installer_cert2"
+ )).build();
+
+ assertThat(FORMULA.matches(appInstallMetadata)).isTrue();
+ }
+
+ @Test
+ public void testFormulaMatches_installerAndCertDoesNotMatchInManifest() {
+ AppInstallMetadata appInstallMetadata = getAppInstallMetadataBuilder()
+ .setInstallerName("installer1")
+ .setInstallerCertificates(Arrays.asList("installer_cert1", "random_cert"))
+ .setAllowedInstallersAndCert(ImmutableMap.of(
+ "installer1", "installer_cert2",
+ "installer2", "installer_cert1"
+ )).build();
+
+ assertThat(FORMULA.matches(appInstallMetadata)).isFalse();
+ }
+
+ @Test
+ public void testFormulaMatches_installerNotInManifest() {
+ AppInstallMetadata appInstallMetadata = getAppInstallMetadataBuilder()
+ .setInstallerName("installer3")
+ .setInstallerCertificates(Arrays.asList("installer_cert1", "random_cert"))
+ .setAllowedInstallersAndCert(ImmutableMap.of(
+ "installer1", "installer_cert2",
+ "installer2", "installer_cert1"
+ )).build();
+
+ assertThat(FORMULA.matches(appInstallMetadata)).isFalse();
+ }
+
+ @Test
+ public void testFormulaMatches_certificateNotInManifest() {
+ AppInstallMetadata appInstallMetadata = getAppInstallMetadataBuilder()
+ .setInstallerName("installer1")
+ .setInstallerCertificates(Arrays.asList("installer_cert3", "random_cert"))
+ .setAllowedInstallersAndCert(ImmutableMap.of(
+ "installer1", "installer_cert2",
+ "installer2", "installer_cert1"
+ )).build();
+
+ assertThat(FORMULA.matches(appInstallMetadata)).isFalse();
+ }
+
+ @Test
+ public void testFormulaMatches_emptyManifest() {
+ AppInstallMetadata appInstallMetadata = getAppInstallMetadataBuilder()
+ .setInstallerName("installer1")
+ .setInstallerCertificates(Arrays.asList("installer_cert3", "random_cert"))
+ .setAllowedInstallersAndCert(ImmutableMap.of()).build();
+
+ assertThat(FORMULA.matches(appInstallMetadata)).isTrue();
+ }
+
+ /** Returns a builder with all fields filled with some dummy data. */
+ private AppInstallMetadata.Builder getAppInstallMetadataBuilder() {
+ return new AppInstallMetadata.Builder()
+ .setPackageName("abc")
+ .setAppCertificates(Collections.emptyList())
+ .setInstallerCertificates(Collections.emptyList())
+ .setInstallerName("abc")
+ .setVersionCode(-1)
+ .setIsPreInstalled(true);
+ }
+}
diff --git a/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java b/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java
index 75ef1f22b819..62c9c98f4e1d 100644
--- a/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java
+++ b/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java
@@ -20,8 +20,6 @@ import static android.content.integrity.IntegrityFormula.COMPOUND_FORMULA_TAG;
import static com.google.common.truth.Truth.assertThat;
-import static org.testng.Assert.assertThrows;
-
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -32,8 +30,7 @@ public class IntegrityFormulaTest {
@Test
public void createEqualsFormula_packageName() {
String packageName = "com.test.app";
- IntegrityFormula formula =
- IntegrityFormula.PACKAGE_NAME.equalTo(packageName);
+ IntegrityFormula formula = IntegrityFormula.Application.packageNameEquals(packageName);
AtomicFormula.StringAtomicFormula stringAtomicFormula =
(AtomicFormula.StringAtomicFormula) formula;
@@ -46,8 +43,7 @@ public class IntegrityFormulaTest {
@Test
public void createEqualsFormula_appCertificate() {
String appCertificate = "com.test.app";
- IntegrityFormula formula =
- IntegrityFormula.APP_CERTIFICATE.equalTo(appCertificate);
+ IntegrityFormula formula = IntegrityFormula.Application.certificatesContain(appCertificate);
AtomicFormula.StringAtomicFormula stringAtomicFormula =
(AtomicFormula.StringAtomicFormula) formula;
@@ -60,8 +56,7 @@ public class IntegrityFormulaTest {
@Test
public void createEqualsFormula_installerName() {
String installerName = "com.test.app";
- IntegrityFormula formula =
- IntegrityFormula.INSTALLER_NAME.equalTo(installerName);
+ IntegrityFormula formula = IntegrityFormula.Installer.packageNameEquals(installerName);
AtomicFormula.StringAtomicFormula stringAtomicFormula =
(AtomicFormula.StringAtomicFormula) formula;
@@ -75,7 +70,7 @@ public class IntegrityFormulaTest {
public void createEqualsFormula_installerCertificate() {
String installerCertificate = "com.test.app";
IntegrityFormula formula =
- IntegrityFormula.INSTALLER_CERTIFICATE.equalTo(installerCertificate);
+ IntegrityFormula.Installer.certificatesContain(installerCertificate);
AtomicFormula.StringAtomicFormula stringAtomicFormula =
(AtomicFormula.StringAtomicFormula) formula;
@@ -88,8 +83,7 @@ public class IntegrityFormulaTest {
@Test
public void createEqualsFormula_versionCode() {
int versionCode = 12;
- IntegrityFormula formula =
- IntegrityFormula.VERSION_CODE.equalTo(versionCode);
+ IntegrityFormula formula = IntegrityFormula.Application.versionCodeEquals(versionCode);
AtomicFormula.LongAtomicFormula stringAtomicFormula =
(AtomicFormula.LongAtomicFormula) formula;
@@ -100,24 +94,9 @@ public class IntegrityFormulaTest {
}
@Test
- public void createEqualsFormula_invalidKeyTypeForStringParameter() {
- assertThrows(
- IllegalArgumentException.class,
- () -> IntegrityFormula.PRE_INSTALLED.equalTo("wrongString"));
- }
-
- @Test
- public void createEqualsFormula_invalidKeyTypeForLongParameter() {
- assertThrows(
- IllegalArgumentException.class,
- () -> IntegrityFormula.PACKAGE_NAME.equalTo(12));
- }
-
- @Test
public void createGreaterThanFormula_versionCode() {
int versionCode = 12;
- IntegrityFormula formula =
- IntegrityFormula.VERSION_CODE.greaterThan(versionCode);
+ IntegrityFormula formula = IntegrityFormula.Application.versionCodeGreaterThan(versionCode);
AtomicFormula.LongAtomicFormula stringAtomicFormula =
(AtomicFormula.LongAtomicFormula) formula;
@@ -128,17 +107,10 @@ public class IntegrityFormulaTest {
}
@Test
- public void createGreaterThanFormula_invalidKeyTypeForLongParameter() {
- assertThrows(
- IllegalArgumentException.class,
- () -> IntegrityFormula.PACKAGE_NAME.greaterThan(12));
- }
-
- @Test
public void createGreaterThanOrEqualsToFormula_versionCode() {
int versionCode = 12;
- IntegrityFormula formula =
- IntegrityFormula.VERSION_CODE.greaterThanOrEquals(versionCode);
+ IntegrityFormula formula = IntegrityFormula.Application.versionCodeGreaterThanOrEqualTo(
+ versionCode);
AtomicFormula.LongAtomicFormula stringAtomicFormula =
(AtomicFormula.LongAtomicFormula) formula;
@@ -149,15 +121,8 @@ public class IntegrityFormulaTest {
}
@Test
- public void createGreaterThanOrEqualsToFormula_invalidKeyTypeForLongParameter() {
- assertThrows(
- IllegalArgumentException.class,
- () -> IntegrityFormula.PACKAGE_NAME.greaterThanOrEquals(12));
- }
-
- @Test
public void createIsTrueFormula_preInstalled() {
- IntegrityFormula formula = IntegrityFormula.PRE_INSTALLED.equalTo(true);
+ IntegrityFormula formula = IntegrityFormula.Application.isPreInstalled();
AtomicFormula.BooleanAtomicFormula stringAtomicFormula =
(AtomicFormula.BooleanAtomicFormula) formula;
@@ -167,20 +132,12 @@ public class IntegrityFormulaTest {
}
@Test
- public void createIsTrueFormula_invalidKeyTypeForBoolParameter() {
- assertThrows(
- IllegalArgumentException.class,
- () -> IntegrityFormula.PACKAGE_NAME.equalTo(true));
- }
-
- @Test
public void createAllFormula() {
String packageName = "com.test.package";
String certificateName = "certificate";
- IntegrityFormula formula1 =
- IntegrityFormula.PACKAGE_NAME.equalTo(packageName);
- IntegrityFormula formula2 =
- IntegrityFormula.APP_CERTIFICATE.equalTo(certificateName);
+ IntegrityFormula formula1 = IntegrityFormula.Application.packageNameEquals(packageName);
+ IntegrityFormula formula2 = IntegrityFormula.Application.certificatesContain(
+ certificateName);
IntegrityFormula compoundFormula = IntegrityFormula.all(formula1, formula2);
@@ -191,10 +148,9 @@ public class IntegrityFormulaTest {
public void createAnyFormula() {
String packageName = "com.test.package";
String certificateName = "certificate";
- IntegrityFormula formula1 =
- IntegrityFormula.PACKAGE_NAME.equalTo(packageName);
- IntegrityFormula formula2 =
- IntegrityFormula.APP_CERTIFICATE.equalTo(certificateName);
+ IntegrityFormula formula1 = IntegrityFormula.Application.packageNameEquals(packageName);
+ IntegrityFormula formula2 = IntegrityFormula.Application.certificatesContain(
+ certificateName);
IntegrityFormula compoundFormula = IntegrityFormula.any(formula1, formula2);
@@ -206,8 +162,7 @@ public class IntegrityFormulaTest {
String packageName = "com.test.package";
IntegrityFormula compoundFormula =
- IntegrityFormula.not(
- IntegrityFormula.PACKAGE_NAME.equalTo(packageName));
+ IntegrityFormula.not(IntegrityFormula.Application.packageNameEquals(packageName));
assertThat(compoundFormula.getTag()).isEqualTo(COMPOUND_FORMULA_TAG);
}
diff --git a/core/tests/coretests/src/android/content/res/ConfigurationTest.java b/core/tests/coretests/src/android/content/res/ConfigurationTest.java
index 2c956c990e97..669138c15698 100644
--- a/core/tests/coretests/src/android/content/res/ConfigurationTest.java
+++ b/core/tests/coretests/src/android/content/res/ConfigurationTest.java
@@ -148,6 +148,15 @@ public class ConfigurationTest extends TestCase {
assertEquals(SMALLEST_SCREEN_WIDTH_DP_UNDEFINED, config.smallestScreenWidthDp);
}
+ @Test
+ public void testNightModeHelper() {
+ Configuration config = new Configuration();
+ config.uiMode = Configuration.UI_MODE_NIGHT_YES;
+ assertTrue(config.isNightModeActive());
+ config.uiMode = Configuration.UI_MODE_NIGHT_NO;
+ assertFalse(config.isNightModeActive());
+ }
+
private void dumpDebug(File f, Configuration config) throws Exception {
final AtomicFile af = new AtomicFile(f);
FileOutputStream fos = af.startWrite();
diff --git a/core/tests/coretests/src/android/database/DatabaseGeneralTest.java b/core/tests/coretests/src/android/database/DatabaseGeneralTest.java
index 49fb75bf6a45..b8dbfd3186c5 100644
--- a/core/tests/coretests/src/android/database/DatabaseGeneralTest.java
+++ b/core/tests/coretests/src/android/database/DatabaseGeneralTest.java
@@ -466,179 +466,7 @@ public class DatabaseGeneralTest extends AndroidTestCase implements PerformanceT
// expected
}
}
-
- @MediumTest
- public void testTokenize() throws Exception {
- Cursor c;
- mDatabase.execSQL("CREATE TABLE tokens (" +
- "token TEXT COLLATE unicode," +
- "source INTEGER," +
- "token_index INTEGER," +
- "tag TEXT" +
- ");");
- mDatabase.execSQL("CREATE TABLE tokens_no_index (" +
- "token TEXT COLLATE unicode," +
- "source INTEGER" +
- ");");
-
- Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
- "SELECT _TOKENIZE(NULL, NULL, NULL, NULL)", null));
- Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
- "SELECT _TOKENIZE('tokens', NULL, NULL, NULL)", null));
- Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
- "SELECT _TOKENIZE('tokens', 10, NULL, NULL)", null));
- Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
- "SELECT _TOKENIZE('tokens', 10, 'some string', NULL)", null));
-
- Assert.assertEquals(3, DatabaseUtils.longForQuery(mDatabase,
- "SELECT _TOKENIZE('tokens', 11, 'some string ok', ' ', 1, 'foo')", null));
- Assert.assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
- "SELECT _TOKENIZE('tokens', 11, 'second field', ' ', 1, 'bar')", null));
-
- Assert.assertEquals(3, DatabaseUtils.longForQuery(mDatabase,
- "SELECT _TOKENIZE('tokens_no_index', 20, 'some string ok', ' ')", null));
- Assert.assertEquals(3, DatabaseUtils.longForQuery(mDatabase,
- "SELECT _TOKENIZE('tokens_no_index', 21, 'foo bar baz', ' ', 0)", null));
-
- // test Chinese
- String chinese = new String("\u4eac\u4ec5 \u5c3d\u5f84\u60ca");
- Assert.assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
- "SELECT _TOKENIZE('tokens', 12,'" + chinese + "', ' ', 1)", null));
-
- String icustr = new String("Fr\u00e9d\u00e9ric Hj\u00f8nnev\u00e5g");
-
- Assert.assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
- "SELECT _TOKENIZE('tokens', 13, '" + icustr + "', ' ', 1)", null));
-
- Assert.assertEquals(9, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens;", null));
-
- String key = DatabaseUtils.getHexCollationKey("Frederic Hjonneva");
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(13, DatabaseUtils.longForQuery(mDatabase,
- "SELECT source from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
- "SELECT token_index from tokens where token GLOB '" + key + "*'", null));
- key = DatabaseUtils.getHexCollationKey("Hjonneva");
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(13, DatabaseUtils.longForQuery(mDatabase,
- "SELECT source from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT token_index from tokens where token GLOB '" + key + "*'", null));
-
- key = DatabaseUtils.getHexCollationKey("some string ok");
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase,
- "SELECT source from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
- "SELECT token_index from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals("foo", DatabaseUtils.stringForQuery(mDatabase,
- "SELECT tag from tokens where token GLOB '" + key + "*'", null));
- key = DatabaseUtils.getHexCollationKey("string");
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase,
- "SELECT source from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT token_index from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals("foo", DatabaseUtils.stringForQuery(mDatabase,
- "SELECT tag from tokens where token GLOB '" + key + "*'", null));
- key = DatabaseUtils.getHexCollationKey("ok");
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase,
- "SELECT source from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
- "SELECT token_index from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals("foo", DatabaseUtils.stringForQuery(mDatabase,
- "SELECT tag from tokens where token GLOB '" + key + "*'", null));
-
- key = DatabaseUtils.getHexCollationKey("second field");
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase,
- "SELECT source from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
- "SELECT token_index from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals("bar", DatabaseUtils.stringForQuery(mDatabase,
- "SELECT tag from tokens where token GLOB '" + key + "*'", null));
- key = DatabaseUtils.getHexCollationKey("field");
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase,
- "SELECT source from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT token_index from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals("bar", DatabaseUtils.stringForQuery(mDatabase,
- "SELECT tag from tokens where token GLOB '" + key + "*'", null));
-
- key = DatabaseUtils.getHexCollationKey(chinese);
- String[] a = new String[1];
- a[0] = key;
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens where token= ?", a));
- Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase,
- "SELECT source from tokens where token= ?", a));
- Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
- "SELECT token_index from tokens where token= ?", a));
- a[0] += "*";
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens where token GLOB ?", a));
- Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase,
- "SELECT source from tokens where token GLOB ?", a));
- Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
- "SELECT token_index from tokens where token GLOB ?", a));
-
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens where token= '" + key + "'", null));
- Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase,
- "SELECT source from tokens where token= '" + key + "'", null));
- Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
- "SELECT token_index from tokens where token= '" + key + "'", null));
-
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase,
- "SELECT source from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
- "SELECT token_index from tokens where token GLOB '" + key + "*'", null));
-
- key = DatabaseUtils.getHexCollationKey("\u4eac\u4ec5");
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase,
- "SELECT source from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
- "SELECT token_index from tokens where token GLOB '" + key + "*'", null));
-
- key = DatabaseUtils.getHexCollationKey("\u5c3d\u5f84\u60ca");
- Log.d("DatabaseGeneralTest", "key = " + key);
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase,
- "SELECT source from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT token_index from tokens where token GLOB '" + key + "*'", null));
-
- Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens where token GLOB 'ab*'", null));
-
- key = DatabaseUtils.getHexCollationKey("some string ok");
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens_no_index where token GLOB '" + key + "*'", null));
- Assert.assertEquals(20, DatabaseUtils.longForQuery(mDatabase,
- "SELECT source from tokens_no_index where token GLOB '" + key + "*'", null));
-
- key = DatabaseUtils.getHexCollationKey("bar");
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens_no_index where token GLOB '" + key + "*'", null));
- Assert.assertEquals(21, DatabaseUtils.longForQuery(mDatabase,
- "SELECT source from tokens_no_index where token GLOB '" + key + "*'", null));
- }
-
+
@MediumTest
public void testTransactions() throws Exception {
mDatabase.execSQL("CREATE TABLE test (num INTEGER);");
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
index a2d23558616b..8dbb5f544ad4 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
@@ -206,6 +206,7 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi {
blockModes,
userAuthenticationRequired,
(int) userAuthenticationValidityDurationSeconds,
+ keymasterHwEnforcedUserAuthenticators,
userAuthenticationRequirementEnforcedBySecureHardware,
userAuthenticationValidWhileOnBody,
trustedUserPresenceRequred,
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 450dd3301253..d683041fbfdc 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -263,6 +263,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu
private final boolean mRandomizedEncryptionRequired;
private final boolean mUserAuthenticationRequired;
private final int mUserAuthenticationValidityDurationSeconds;
+ private final @KeyProperties.AuthEnum int mUserAuthenticationType;
private final boolean mUserPresenceRequired;
private final byte[] mAttestationChallenge;
private final boolean mUniqueIdIncluded;
@@ -301,6 +302,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu
boolean randomizedEncryptionRequired,
boolean userAuthenticationRequired,
int userAuthenticationValidityDurationSeconds,
+ @KeyProperties.AuthEnum int userAuthenticationType,
boolean userPresenceRequired,
byte[] attestationChallenge,
boolean uniqueIdIncluded,
@@ -352,6 +354,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu
mUserAuthenticationRequired = userAuthenticationRequired;
mUserPresenceRequired = userPresenceRequired;
mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
+ mUserAuthenticationType = userAuthenticationType;
mAttestationChallenge = Utils.cloneIfNotNull(attestationChallenge);
mUniqueIdIncluded = uniqueIdIncluded;
mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody;
@@ -605,6 +608,22 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu
}
/**
+ * Gets the modes of authentication that can authorize use of this key. This has effect only if
+ * user authentication is required (see {@link #isUserAuthenticationRequired()}).
+ *
+ * <p>This authorization applies only to secret key and private key operations. Public key
+ * operations are not restricted.
+ *
+ * @return integer representing the bitwse OR of all acceptable authentication types for the
+ * key.
+ *
+ * @see #isUserAuthenticationRequired()
+ * @see Builder#setUserAuthenticationParameters(int, int)
+ */
+ public @KeyProperties.AuthEnum int getUserAuthenticationType() {
+ return mUserAuthenticationType;
+ }
+ /**
* Returns {@code true} if the key is authorized to be used only if a test of user presence has
* been performed between the {@code Signature.initSign()} and {@code Signature.sign()} calls.
* It requires that the KeyStore implementation have a direct way to validate the user presence
@@ -746,6 +765,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu
private boolean mRandomizedEncryptionRequired = true;
private boolean mUserAuthenticationRequired;
private int mUserAuthenticationValidityDurationSeconds = -1;
+ private @KeyProperties.AuthEnum int mUserAuthenticationType;
private boolean mUserPresenceRequired = false;
private byte[] mAttestationChallenge = null;
private boolean mUniqueIdIncluded = false;
@@ -810,6 +830,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu
mUserAuthenticationRequired = sourceSpec.isUserAuthenticationRequired();
mUserAuthenticationValidityDurationSeconds =
sourceSpec.getUserAuthenticationValidityDurationSeconds();
+ mUserAuthenticationType = sourceSpec.getUserAuthenticationType();
mUserPresenceRequired = sourceSpec.isUserPresenceRequired();
mAttestationChallenge = sourceSpec.getAttestationChallenge();
mUniqueIdIncluded = sourceSpec.isUniqueIdIncluded();
@@ -1207,14 +1228,62 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu
* @see BiometricPrompt
* @see BiometricPrompt.CryptoObject
* @see KeyguardManager
+ * @deprecated See {@link #setUserAuthenticationParameters(int, int)}
*/
+ @Deprecated
@NonNull
public Builder setUserAuthenticationValidityDurationSeconds(
@IntRange(from = -1) int seconds) {
if (seconds < -1) {
throw new IllegalArgumentException("seconds must be -1 or larger");
}
- mUserAuthenticationValidityDurationSeconds = seconds;
+ if (seconds == -1) {
+ return setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG);
+ }
+ return setUserAuthenticationParameters(seconds, KeyProperties.AUTH_BIOMETRIC_STRONG);
+ }
+
+ /**
+ * Sets the duration of time (seconds) and authorization type for which this key is
+ * authorized to be used after the user is successfully authenticated. This has effect if
+ * the key requires user authentication for its use (see
+ * {@link #setUserAuthenticationRequired(boolean)}).
+ *
+ * <p>By default, if user authentication is required, it must take place for every use of
+ * the key.
+ *
+ * <p>These cryptographic operations will throw {@link UserNotAuthenticatedException} during
+ * initialization if the user needs to be authenticated to proceed. This situation can be
+ * resolved by the user authenticating with the appropriate biometric or credential as
+ * required by the key. See {@link BiometricPrompt.Builder#setAllowedAuthenticators(int)}
+ * and {@link BiometricManager.Authenticators}.
+ *
+ * <p>Once resolved, initializing a new cryptographic operation using this key (or any other
+ * key which is authorized to be used for a fixed duration of time after user
+ * authentication) should succeed provided the user authentication flow completed
+ * successfully.
+ *
+ * @param timeout duration in seconds or {@code 0} if user authentication must take place
+ * for every use of the key. {@code -1} is also accepted for legacy purposes. It is
+ * functionally the same as {@code 0}.
+ * @param type set of authentication types which can authorize use of the key. See
+ * {@link KeyProperties}.{@code AUTH} flags.
+ *
+ * @see #setUserAuthenticationRequired(boolean)
+ * @see BiometricPrompt
+ * @see BiometricPrompt.CryptoObject
+ * @see KeyguardManager
+ */
+ @NonNull
+ public Builder setUserAuthenticationParameters(@IntRange(from = -1) int timeout,
+ @KeyProperties.AuthEnum int type) {
+ if (timeout < -1) {
+ throw new IllegalArgumentException("timeout must be -1 or larger");
+ } else if (timeout == -1) {
+ timeout = 0;
+ }
+ mUserAuthenticationValidityDurationSeconds = timeout;
+ mUserAuthenticationType = type;
return this;
}
@@ -1392,6 +1461,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu
mRandomizedEncryptionRequired,
mUserAuthenticationRequired,
mUserAuthenticationValidityDurationSeconds,
+ mUserAuthenticationType,
mUserPresenceRequired,
mAttestationChallenge,
mUniqueIdIncluded,
diff --git a/keystore/java/android/security/keystore/KeyInfo.java b/keystore/java/android/security/keystore/KeyInfo.java
index 0a75cd5268b7..d891a25dba68 100644
--- a/keystore/java/android/security/keystore/KeyInfo.java
+++ b/keystore/java/android/security/keystore/KeyInfo.java
@@ -78,6 +78,7 @@ public class KeyInfo implements KeySpec {
private final @KeyProperties.BlockModeEnum String[] mBlockModes;
private final boolean mUserAuthenticationRequired;
private final int mUserAuthenticationValidityDurationSeconds;
+ private final @KeyProperties.AuthEnum int mUserAuthenticationType;
private final boolean mUserAuthenticationRequirementEnforcedBySecureHardware;
private final boolean mUserAuthenticationValidWhileOnBody;
private final boolean mTrustedUserPresenceRequired;
@@ -101,6 +102,7 @@ public class KeyInfo implements KeySpec {
@KeyProperties.BlockModeEnum String[] blockModes,
boolean userAuthenticationRequired,
int userAuthenticationValidityDurationSeconds,
+ @KeyProperties.AuthEnum int userAuthenticationType,
boolean userAuthenticationRequirementEnforcedBySecureHardware,
boolean userAuthenticationValidWhileOnBody,
boolean trustedUserPresenceRequired,
@@ -122,6 +124,7 @@ public class KeyInfo implements KeySpec {
mBlockModes = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(blockModes));
mUserAuthenticationRequired = userAuthenticationRequired;
mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
+ mUserAuthenticationType = userAuthenticationType;
mUserAuthenticationRequirementEnforcedBySecureHardware =
userAuthenticationRequirementEnforcedBySecureHardware;
mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody;
@@ -301,6 +304,22 @@ public class KeyInfo implements KeySpec {
}
/**
+ * Gets the acceptable user authentication types for which this key can be authorized to be
+ * used. This has effect only if user authentication is required (see
+ * {@link #isUserAuthenticationRequired()}).
+ *
+ * <p>This authorization applies only to secret key and private key operations. Public key
+ * operations are not restricted.
+ *
+ * @return integer representing the accepted forms of user authentication for this key
+ *
+ * @see #isUserAuthenticationRequired()
+ */
+ public @KeyProperties.AuthEnum int getUserAuthenticationType() {
+ return mUserAuthenticationType;
+ }
+
+ /**
* Returns {@code true} if the requirement that this key can only be used if the user has been
* authenticated is enforced by secure hardware (e.g., Trusted Execution Environment (TEE) or
* Secure Element (SE)).
diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java
index f12a659039ee..c58a1236d475 100644
--- a/keystore/java/android/security/keystore/KeyProperties.java
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -39,6 +39,27 @@ public abstract class KeyProperties {
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "AUTH_" }, value = {
+ AUTH_BIOMETRIC_STRONG,
+ AUTH_DEVICE_CREDENTIAL,
+ })
+ public @interface AuthEnum {}
+
+ /**
+ * The non-biometric credential used to secure the device (i.e., PIN, pattern, or password)
+ */
+ public static final int AUTH_DEVICE_CREDENTIAL = 1 << 0;
+
+ /**
+ * Any biometric (e.g. fingerprint, iris, or face) on the device that meets or exceeds the
+ * requirements for <strong>Strong</strong>, as defined by the Android CDD.
+ */
+ public static final int AUTH_BIOMETRIC_STRONG = 1 << 1;
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, prefix = { "PURPOSE_" }, value = {
PURPOSE_ENCRYPT,
PURPOSE_DECRYPT,
diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java
index 26181a65dc1d..e230b7c3708b 100644
--- a/keystore/java/android/security/keystore/KeyProtection.java
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -225,6 +225,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs {
private final @KeyProperties.BlockModeEnum String[] mBlockModes;
private final boolean mRandomizedEncryptionRequired;
private final boolean mUserAuthenticationRequired;
+ private final @KeyProperties.AuthEnum int mUserAuthenticationType;
private final int mUserAuthenticationValidityDurationSeconds;
private final boolean mUserPresenceRequred;
private final boolean mUserAuthenticationValidWhileOnBody;
@@ -246,6 +247,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs {
@KeyProperties.BlockModeEnum String[] blockModes,
boolean randomizedEncryptionRequired,
boolean userAuthenticationRequired,
+ @KeyProperties.AuthEnum int userAuthenticationType,
int userAuthenticationValidityDurationSeconds,
boolean userPresenceRequred,
boolean userAuthenticationValidWhileOnBody,
@@ -267,6 +269,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs {
mBlockModes = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(blockModes));
mRandomizedEncryptionRequired = randomizedEncryptionRequired;
mUserAuthenticationRequired = userAuthenticationRequired;
+ mUserAuthenticationType = userAuthenticationType;
mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
mUserPresenceRequred = userPresenceRequred;
mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody;
@@ -429,6 +432,10 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs {
return mUserConfirmationRequired;
}
+ public @KeyProperties.AuthEnum int getUserAuthenticationType() {
+ return mUserAuthenticationType;
+ }
+
/**
* Gets the duration of time (seconds) for which this key is authorized to be used after the
* user is successfully authenticated. This has effect only if user authentication is required
@@ -555,6 +562,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs {
private @KeyProperties.BlockModeEnum String[] mBlockModes;
private boolean mRandomizedEncryptionRequired = true;
private boolean mUserAuthenticationRequired;
+ private @KeyProperties.AuthEnum int mUserAuthenticationType;
private int mUserAuthenticationValidityDurationSeconds = -1;
private boolean mUserPresenceRequired = false;
private boolean mUserAuthenticationValidWhileOnBody;
@@ -850,14 +858,62 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs {
* @see BiometricPrompt
* @see BiometricPrompt.CryptoObject
* @see KeyguardManager
+ * @deprecated See {@link #setUserAuthenticationParameters(int, int)}
*/
+ @Deprecated
@NonNull
public Builder setUserAuthenticationValidityDurationSeconds(
@IntRange(from = -1) int seconds) {
if (seconds < -1) {
throw new IllegalArgumentException("seconds must be -1 or larger");
}
- mUserAuthenticationValidityDurationSeconds = seconds;
+ if (seconds == -1) {
+ return setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG);
+ }
+ return setUserAuthenticationParameters(seconds, KeyProperties.AUTH_BIOMETRIC_STRONG);
+ }
+
+ /**
+ * Sets the duration of time (seconds) and authorization type for which this key is
+ * authorized to be used after the user is successfully authenticated. This has effect if
+ * the key requires user authentication for its use (see
+ * {@link #setUserAuthenticationRequired(boolean)}).
+ *
+ * <p>By default, if user authentication is required, it must take place for every use of
+ * the key.
+ *
+ * <p>These cryptographic operations will throw {@link UserNotAuthenticatedException} during
+ * initialization if the user needs to be authenticated to proceed. This situation can be
+ * resolved by the user authenticating with the appropriate biometric or credential as
+ * required by the key. See {@link BiometricPrompt.Builder#setAllowedAuthenticators(int)}
+ * and {@link BiometricManager.Authenticators}.
+ *
+ * <p>Once resolved, initializing a new cryptographic operation using this key (or any other
+ * key which is authorized to be used for a fixed duration of time after user
+ * authentication) should succeed provided the user authentication flow completed
+ * successfully.
+ *
+ * @param timeout duration in seconds or {@code 0} if user authentication must take place
+ * for every use of the key. {@code -1} is also accepted for legacy purposes. It is
+ * functionally the same as {@code 0}.
+ * @param type set of authentication types which can authorize use of the key. See
+ * {@link KeyProperties}.{@code AUTH} flags.
+ *
+ * @see #setUserAuthenticationRequired(boolean)
+ * @see BiometricPrompt
+ * @see BiometricPrompt.CryptoObject
+ * @see KeyguardManager
+ */
+ @NonNull
+ public Builder setUserAuthenticationParameters(@IntRange(from = -1) int timeout,
+ @KeyProperties.AuthEnum int type) {
+ if (timeout < -1) {
+ throw new IllegalArgumentException("timeout must be -1 or larger");
+ } else if (timeout == -1) {
+ timeout = 0;
+ }
+ mUserAuthenticationValidityDurationSeconds = timeout;
+ mUserAuthenticationType = type;
return this;
}
@@ -1002,6 +1058,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs {
mBlockModes,
mRandomizedEncryptionRequired,
mUserAuthenticationRequired,
+ mUserAuthenticationType,
mUserAuthenticationValidityDurationSeconds,
mUserPresenceRequired,
mUserAuthenticationValidWhileOnBody,
diff --git a/keystore/java/android/security/keystore/KeymasterUtils.java b/keystore/java/android/security/keystore/KeymasterUtils.java
index 79e48cdfdd8e..37b1f23ba55a 100644
--- a/keystore/java/android/security/keystore/KeymasterUtils.java
+++ b/keystore/java/android/security/keystore/KeymasterUtils.java
@@ -88,17 +88,9 @@ public abstract class KeymasterUtils {
* Adds keymaster arguments to express the key's authorization policy supported by user
* authentication.
*
- * @param userAuthenticationRequired whether user authentication is required to authorize the
- * use of the key.
- * @param userAuthenticationValidityDurationSeconds duration of time (seconds) for which user
- * authentication is valid as authorization for using the key or {@code -1} if every
- * use of the key needs authorization.
- * @param boundToSpecificSecureUserId if non-zero, specify which SID the key will be bound to,
- * overriding the default logic in this method where the key is bound to either the root
- * SID of the current user, or the fingerprint SID if explicit fingerprint authorization
- * is requested.
- * @param userConfirmationRequired whether user confirmation is required to authorize the use
- * of the key.
+ * @param args The arguments sent to keymaster that need to be populated from the spec
+ * @param spec The user authentication relevant portions of the spec passed in from the caller.
+ * This spec will be translated into the relevant keymaster tags to be loaded into args.
* @throws IllegalStateException if user authentication is required but the system is in a wrong
* state (e.g., secure lock screen not set up) for generating or importing keys that
* require user authentication.
@@ -122,7 +114,7 @@ public abstract class KeymasterUtils {
return;
}
- if (spec.getUserAuthenticationValidityDurationSeconds() == -1) {
+ if (spec.getUserAuthenticationValidityDurationSeconds() == 0) {
PackageManager pm = KeyStore.getApplicationContext().getPackageManager();
// Every use of this key needs to be authorized by the user. This currently means
// fingerprint or face auth.
@@ -168,7 +160,8 @@ public abstract class KeymasterUtils {
args.addUnsignedLong(KeymasterDefs.KM_TAG_USER_SECURE_ID,
KeymasterArguments.toUint64(sids.get(i)));
}
- args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, KeymasterDefs.HW_AUTH_BIOMETRIC);
+
+ args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, spec.getUserAuthenticationType());
if (spec.isUserAuthenticationValidWhileOnBody()) {
throw new ProviderException("Key validity extension while device is on-body is not "
diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
index 98e458930a7f..9c9773e5d145 100644
--- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
@@ -97,6 +97,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable {
out.writeBoolean(mSpec.isRandomizedEncryptionRequired());
out.writeBoolean(mSpec.isUserAuthenticationRequired());
out.writeInt(mSpec.getUserAuthenticationValidityDurationSeconds());
+ out.writeInt(mSpec.getUserAuthenticationType());
out.writeBoolean(mSpec.isUserPresenceRequired());
out.writeByteArray(mSpec.getAttestationChallenge());
out.writeBoolean(mSpec.isUniqueIdIncluded());
@@ -153,6 +154,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable {
final boolean randomizedEncryptionRequired = in.readBoolean();
final boolean userAuthenticationRequired = in.readBoolean();
final int userAuthenticationValidityDurationSeconds = in.readInt();
+ final int userAuthenticationTypes = in.readInt();
final boolean userPresenceRequired = in.readBoolean();
final byte[] attestationChallenge = in.createByteArray();
final boolean uniqueIdIncluded = in.readBoolean();
@@ -185,6 +187,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable {
randomizedEncryptionRequired,
userAuthenticationRequired,
userAuthenticationValidityDurationSeconds,
+ userAuthenticationTypes,
userPresenceRequired,
attestationChallenge,
uniqueIdIncluded,
diff --git a/keystore/java/android/security/keystore/UserAuthArgs.java b/keystore/java/android/security/keystore/UserAuthArgs.java
index 69520606f101..c9e9bf0ab82f 100644
--- a/keystore/java/android/security/keystore/UserAuthArgs.java
+++ b/keystore/java/android/security/keystore/UserAuthArgs.java
@@ -28,6 +28,7 @@ public interface UserAuthArgs {
boolean isUserAuthenticationRequired();
int getUserAuthenticationValidityDurationSeconds();
+ @KeyProperties.AuthEnum int getUserAuthenticationType();
boolean isUserAuthenticationValidWhileOnBody();
boolean isInvalidatedByBiometricEnrollment();
boolean isUserConfirmationRequired();
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 8cfd2d8ca696..32086625a726 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -992,6 +992,11 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) {
return bag;
}
+static bool compare_bag_entries(const ResolvedBag::Entry& entry1,
+ const ResolvedBag::Entry& entry2) {
+ return entry1.key < entry2.key;
+}
+
const ResolvedBag* AssetManager2::GetBag(uint32_t resid, std::vector<uint32_t>& child_resids) {
auto cached_iter = cached_bags_.find(resid);
if (cached_iter != cached_bags_.end()) {
@@ -1027,13 +1032,15 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid, std::vector<uint32_t>&
child_resids.push_back(resid);
uint32_t parent_resid = dtohl(map->parent.ident);
- if (parent_resid == 0 || std::find(child_resids.begin(), child_resids.end(), parent_resid)
+ if (parent_resid == 0U || std::find(child_resids.begin(), child_resids.end(), parent_resid)
!= child_resids.end()) {
- // There is no parent or that a circular dependency exist, meaning there is nothing to
- // inherit and we can do a simple copy of the entries in the map.
+ // There is no parent or a circular dependency exist, meaning there is nothing to inherit and
+ // we can do a simple copy of the entries in the map.
const size_t entry_count = map_entry_end - map_entry;
util::unique_cptr<ResolvedBag> new_bag{reinterpret_cast<ResolvedBag*>(
malloc(sizeof(ResolvedBag) + (entry_count * sizeof(ResolvedBag::Entry))))};
+
+ bool sort_entries = false;
ResolvedBag::Entry* new_entry = new_bag->entries;
for (; map_entry != map_entry_end; ++map_entry) {
uint32_t new_key = dtohl(map_entry->name.ident);
@@ -1059,8 +1066,15 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid, std::vector<uint32_t>&
new_entry->value.data, new_key);
return nullptr;
}
+ sort_entries = sort_entries ||
+ (new_entry != new_bag->entries && (new_entry->key < (new_entry - 1U)->key));
++new_entry;
}
+
+ if (sort_entries) {
+ std::sort(new_bag->entries, new_bag->entries + entry_count, compare_bag_entries);
+ }
+
new_bag->type_spec_flags = entry.type_flags;
new_bag->entry_count = static_cast<uint32_t>(entry_count);
ResolvedBag* result = new_bag.get();
@@ -1091,6 +1105,7 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid, std::vector<uint32_t>&
const ResolvedBag::Entry* const parent_entry_end = parent_entry + parent_bag->entry_count;
// The keys are expected to be in sorted order. Merge the two bags.
+ bool sort_entries = false;
while (map_entry != map_entry_end && parent_entry != parent_entry_end) {
uint32_t child_key = dtohl(map_entry->name.ident);
if (!is_internal_resid(child_key)) {
@@ -1123,6 +1138,8 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid, std::vector<uint32_t>&
memcpy(new_entry, parent_entry, sizeof(*new_entry));
}
+ sort_entries = sort_entries ||
+ (new_entry != new_bag->entries && (new_entry->key < (new_entry - 1U)->key));
if (child_key >= parent_entry->key) {
// Move to the next parent entry if we used it or it was overridden.
++parent_entry;
@@ -1153,6 +1170,8 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid, std::vector<uint32_t>&
new_entry->value.dataType, new_entry->value.data, new_key);
return nullptr;
}
+ sort_entries = sort_entries ||
+ (new_entry != new_bag->entries && (new_entry->key < (new_entry - 1U)->key));
++map_entry;
++new_entry;
}
@@ -1172,6 +1191,10 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid, std::vector<uint32_t>&
new_bag.release(), sizeof(ResolvedBag) + (actual_count * sizeof(ResolvedBag::Entry)))));
}
+ if (sort_entries) {
+ std::sort(new_bag->entries, new_bag->entries + actual_count, compare_bag_entries);
+ }
+
// Combine flags from the parent and our own bag.
new_bag->type_spec_flags = entry.type_flags | parent_bag->type_spec_flags;
new_bag->entry_count = static_cast<uint32_t>(actual_count);
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index 2f6f3dfcaf1c..35fea7ab86cb 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -285,6 +285,27 @@ TEST_F(AssetManager2Test, FindsBagResourceFromSharedLibrary) {
EXPECT_EQ(0x03, get_package_id(bag->entries[1].key));
}
+TEST_F(AssetManager2Test, FindsBagResourceFromMultipleSharedLibraries) {
+ AssetManager2 assetmanager;
+
+ // libclient is built with lib_one and then lib_two in order.
+ // Reverse the order to test that proper package ID re-assignment is happening.
+ assetmanager.SetApkAssets(
+ {lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()});
+
+ const ResolvedBag* bag = assetmanager.GetBag(libclient::R::style::ThemeMultiLib);
+ ASSERT_NE(nullptr, bag);
+ ASSERT_EQ(bag->entry_count, 2u);
+
+ // First attribute comes from lib_two.
+ EXPECT_EQ(2, bag->entries[0].cookie);
+ EXPECT_EQ(0x02, get_package_id(bag->entries[0].key));
+
+ // The next two attributes come from lib_one.
+ EXPECT_EQ(2, bag->entries[1].cookie);
+ EXPECT_EQ(0x03, get_package_id(bag->entries[1].key));
+}
+
TEST_F(AssetManager2Test, FindsStyleResourceWithParentFromSharedLibrary) {
AssetManager2 assetmanager;
diff --git a/libs/androidfw/tests/data/lib_two/R.h b/libs/androidfw/tests/data/lib_two/R.h
index 92b9cc10e7a8..fd5a910961cb 100644
--- a/libs/androidfw/tests/data/lib_two/R.h
+++ b/libs/androidfw/tests/data/lib_two/R.h
@@ -30,16 +30,22 @@ struct R {
};
};
+ struct integer {
+ enum : uint32_t {
+ bar = 0x02020000, // default
+ };
+ };
+
struct string {
enum : uint32_t {
- LibraryString = 0x02020000, // default
- foo = 0x02020001, // default
+ LibraryString = 0x02030000, // default
+ foo = 0x02030001, // default
};
};
struct style {
enum : uint32_t {
- Theme = 0x02030000, // default
+ Theme = 0x02040000, // default
};
};
};
diff --git a/libs/androidfw/tests/data/lib_two/lib_two.apk b/libs/androidfw/tests/data/lib_two/lib_two.apk
index 486c23000276..8193db637eed 100644
--- a/libs/androidfw/tests/data/lib_two/lib_two.apk
+++ b/libs/androidfw/tests/data/lib_two/lib_two.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/lib_two/res/values/values.xml b/libs/androidfw/tests/data/lib_two/res/values/values.xml
index 340d14c34c5d..4e1d69aa5a5a 100644
--- a/libs/androidfw/tests/data/lib_two/res/values/values.xml
+++ b/libs/androidfw/tests/data/lib_two/res/values/values.xml
@@ -18,14 +18,17 @@
<public type="attr" name="attr3" id="0x00010000" />
<attr name="attr3" format="integer" />
- <public type="string" name="LibraryString" id="0x00020000" />
+ <public type="integer" name="bar" id="0x00020000" />
+ <integer name="bar">1337</integer>
+
+ <public type="string" name="LibraryString" id="0x00030000" />
<string name="LibraryString">Hi from library two</string>
- <public type="string" name="foo" id="0x00020001" />
+ <public type="string" name="foo" id="0x00030001" />
<string name="foo">Foo from lib_two</string>
- <public type="style" name="Theme" id="0x02030000" />
+ <public type="style" name="Theme" id="0x00040000" />
<style name="Theme">
- <item name="com.android.lib_two:attr3">800</item>
+ <item name="com.android.lib_two:attr3">@integer/bar</item>
</style>
</resources>
diff --git a/libs/androidfw/tests/data/libclient/R.h b/libs/androidfw/tests/data/libclient/R.h
index 43d1f9bb68e7..e21b3ebae826 100644
--- a/libs/androidfw/tests/data/libclient/R.h
+++ b/libs/androidfw/tests/data/libclient/R.h
@@ -34,6 +34,7 @@ struct R {
struct style {
enum : uint32_t {
Theme = 0x7f020000, // default
+ ThemeMultiLib = 0x7f020001, // default
};
};
diff --git a/libs/androidfw/tests/data/libclient/libclient.apk b/libs/androidfw/tests/data/libclient/libclient.apk
index 17990248e862..4b9a8833c64a 100644
--- a/libs/androidfw/tests/data/libclient/libclient.apk
+++ b/libs/androidfw/tests/data/libclient/libclient.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/libclient/res/values/values.xml b/libs/androidfw/tests/data/libclient/res/values/values.xml
index fead7c323767..a29f473e3c17 100644
--- a/libs/androidfw/tests/data/libclient/res/values/values.xml
+++ b/libs/androidfw/tests/data/libclient/res/values/values.xml
@@ -27,6 +27,12 @@
<item name="bar">@com.android.lib_one:string/foo</item>
</style>
+ <public type="style" name="ThemeMultiLib" id="0x7f020001" />
+ <style name="ThemeMultiLib" >
+ <item name="com.android.lib_one:attr1">@com.android.lib_one:string/foo</item>
+ <item name="com.android.lib_two:attr3">@com.android.lib_two:integer/bar</item>
+ </style>
+
<public type="string" name="foo_one" id="0x7f030000" />
<string name="foo_one">@com.android.lib_one:string/foo</string>
diff --git a/location/java/android/location/AbstractListenerManager.java b/location/java/android/location/AbstractListenerManager.java
index 944ebf937dc8..f075a53829ce 100644
--- a/location/java/android/location/AbstractListenerManager.java
+++ b/location/java/android/location/AbstractListenerManager.java
@@ -27,6 +27,7 @@ import android.util.ArrayMap;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
+import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -35,26 +36,34 @@ import java.util.function.Consumer;
*
* @hide
*/
-abstract class AbstractListenerManager<T> {
+abstract class AbstractListenerManager<TRequest, TListener> {
- private static class Registration<T> {
+ private static class Registration<TRequest, TListener> {
private final Executor mExecutor;
- @Nullable private volatile T mListener;
+ @Nullable private TRequest mRequest;
+ @Nullable private volatile TListener mListener;
- private Registration(Executor executor, T listener) {
+ private Registration(@Nullable TRequest request, Executor executor, TListener listener) {
Preconditions.checkArgument(listener != null, "invalid null listener/callback");
Preconditions.checkArgument(executor != null, "invalid null executor");
mExecutor = executor;
mListener = listener;
+ mRequest = request;
+ }
+
+ @Nullable
+ public TRequest getRequest() {
+ return mRequest;
}
private void unregister() {
+ mRequest = null;
mListener = null;
}
- private void execute(Consumer<T> operation) {
+ private void execute(Consumer<TListener> operation) {
mExecutor.execute(() -> {
- T listener = mListener;
+ TListener listener = mListener;
if (listener == null) {
return;
}
@@ -71,71 +80,135 @@ abstract class AbstractListenerManager<T> {
}
@GuardedBy("mListeners")
- private final ArrayMap<Object, Registration<T>> mListeners = new ArrayMap<>();
+ private final ArrayMap<Object, Registration<TRequest, TListener>> mListeners =
+ new ArrayMap<>();
+
+ @GuardedBy("mListeners")
+ @Nullable
+ private TRequest mMergedRequest;
- public boolean addListener(@NonNull T listener, @NonNull Handler handler)
+ public boolean addListener(@NonNull TListener listener, @NonNull Handler handler)
throws RemoteException {
- return addInternal(listener, handler);
+ return addInternal(/* request= */ null, listener, handler);
}
- public boolean addListener(@NonNull T listener, @NonNull Executor executor)
+ public boolean addListener(@NonNull TListener listener, @NonNull Executor executor)
throws RemoteException {
- return addInternal(listener, executor);
+ return addInternal(/* request= */ null, listener, executor);
}
- protected final boolean addInternal(@NonNull Object listener, @NonNull Handler handler)
- throws RemoteException {
- return addInternal(listener, new HandlerExecutor(handler));
+ public boolean addListener(@Nullable TRequest request, @NonNull TListener listener,
+ @NonNull Handler handler) throws RemoteException {
+ return addInternal(request, listener, handler);
+ }
+
+ public boolean addListener(@Nullable TRequest request, @NonNull TListener listener,
+ @NonNull Executor executor) throws RemoteException {
+ return addInternal(request, listener, executor);
+ }
+
+ protected final boolean addInternal(@Nullable TRequest request, @NonNull Object listener,
+ @NonNull Handler handler) throws RemoteException {
+ return addInternal(request, listener, new HandlerExecutor(handler));
}
- protected final boolean addInternal(@NonNull Object listener, @NonNull Executor executor)
+ protected final boolean addInternal(@Nullable TRequest request, @NonNull Object listener,
+ @NonNull Executor executor)
throws RemoteException {
Preconditions.checkArgument(listener != null, "invalid null listener/callback");
- return addInternal(listener, new Registration<>(executor, convertKey(listener)));
+ return addInternal(listener, new Registration<>(request, executor, convertKey(listener)));
}
- private boolean addInternal(Object key, Registration<T> registration) throws RemoteException {
+ private boolean addInternal(Object key, Registration<TRequest, TListener> registration)
+ throws RemoteException {
Preconditions.checkNotNull(registration);
synchronized (mListeners) {
- if (mListeners.isEmpty() && !registerService()) {
- return false;
- }
- Registration<T> oldRegistration = mListeners.put(key, registration);
+ boolean initialRequest = mListeners.isEmpty();
+
+ Registration<TRequest, TListener> oldRegistration = mListeners.put(key, registration);
if (oldRegistration != null) {
oldRegistration.unregister();
}
+ TRequest merged = mergeRequests();
+
+ if (initialRequest || !Objects.equals(merged, mMergedRequest)) {
+ mMergedRequest = merged;
+ if (!initialRequest) {
+ unregisterService();
+ }
+ registerService(mMergedRequest);
+ }
+
return true;
}
}
public void removeListener(Object listener) throws RemoteException {
synchronized (mListeners) {
- Registration<T> oldRegistration = mListeners.remove(listener);
+ Registration<TRequest, TListener> oldRegistration = mListeners.remove(listener);
if (oldRegistration == null) {
return;
}
oldRegistration.unregister();
- if (mListeners.isEmpty()) {
+ boolean lastRequest = mListeners.isEmpty();
+ TRequest merged = lastRequest ? null : mergeRequests();
+ boolean newRequest = !lastRequest && !Objects.equals(merged, mMergedRequest);
+
+ if (lastRequest || newRequest) {
unregisterService();
+ mMergedRequest = merged;
+ if (newRequest) {
+ registerService(mMergedRequest);
+ }
}
}
}
@SuppressWarnings("unchecked")
- protected T convertKey(@NonNull Object listener) {
- return (T) listener;
+ protected TListener convertKey(@NonNull Object listener) {
+ return (TListener) listener;
}
- protected abstract boolean registerService() throws RemoteException;
+ protected abstract boolean registerService(TRequest request) throws RemoteException;
protected abstract void unregisterService() throws RemoteException;
- protected void execute(Consumer<T> operation) {
+ @Nullable
+ protected TRequest merge(@NonNull TRequest[] requests) {
+ for (TRequest request : requests) {
+ Preconditions.checkArgument(request == null,
+ "merge() has to be overridden for non-null requests.");
+ }
+ return null;
+ }
+
+ protected void execute(Consumer<TListener> operation) {
synchronized (mListeners) {
- for (Registration<T> registration : mListeners.values()) {
+ for (Registration<TRequest, TListener> registration : mListeners.values()) {
registration.execute(operation);
}
}
}
+
+ @GuardedBy("mListeners")
+ @SuppressWarnings("unchecked")
+ @Nullable
+ private TRequest mergeRequests() {
+ Preconditions.checkState(Thread.holdsLock(mListeners));
+
+ if (mListeners.isEmpty()) {
+ return null;
+ }
+
+ if (mListeners.size() == 1) {
+ return mListeners.valueAt(0).getRequest();
+ }
+
+ TRequest[] requests = (TRequest[]) new Object[mListeners.size()];
+ for (int index = 0; index < mListeners.size(); index++) {
+ requests[index] = mListeners.valueAt(index).getRequest();
+ }
+ return merge(requests);
+ }
}
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index 6a5c0ec9457a..a99e68fbb7b6 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -22,6 +22,7 @@ import android.location.Criteria;
import android.location.GeocoderParams;
import android.location.Geofence;
import android.location.GnssMeasurementCorrections;
+import android.location.GnssRequest;
import android.location.IBatchedLocationCallback;
import android.location.IGnssMeasurementsListener;
import android.location.IGnssStatusListener;
@@ -69,8 +70,10 @@ interface ILocationManager
double upperRightLatitude, double upperRightLongitude, int maxResults,
in GeocoderParams params, out List<Address> addrs);
- boolean addGnssMeasurementsListener(in IGnssMeasurementsListener listener,
- String packageName, String featureId, String listenerIdentifier);
+ boolean addGnssMeasurementsListener(in GnssRequest request,
+ in IGnssMeasurementsListener listener,
+ String packageName, String featureId,
+ String listenerIdentifier);
void injectGnssMeasurementCorrections(in GnssMeasurementCorrections corrections,
in String packageName);
long getGnssCapabilities(in String packageName);
@@ -107,6 +110,7 @@ interface ILocationManager
boolean isProviderEnabledForUser(String provider, int userId);
boolean isLocationEnabledForUser(int userId);
+ void setLocationEnabledForUser(boolean enabled, int userId);
void addTestProvider(String name, in ProviderProperties properties, String opPackageName);
void removeTestProvider(String provider, String opPackageName);
void setTestProviderLocation(String provider, in Location loc, String opPackageName);
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 7e6486cc933e..8ae967fe79c2 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -478,13 +478,11 @@ public class LocationManager {
@TestApi
@RequiresPermission(WRITE_SECURE_SETTINGS)
public void setLocationEnabledForUser(boolean enabled, @NonNull UserHandle userHandle) {
- Settings.Secure.putIntForUser(
- mContext.getContentResolver(),
- Settings.Secure.LOCATION_MODE,
- enabled
- ? Settings.Secure.LOCATION_MODE_ON
- : Settings.Secure.LOCATION_MODE_OFF,
- userHandle.getIdentifier());
+ try {
+ mService.setLocationEnabledForUser(enabled, userHandle.getIdentifier());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -2199,7 +2197,7 @@ public class LocationManager {
* Registers a GNSS Measurement callback.
*
* @param request extra parameters to pass to GNSS measurement provider. For example, if {@link
- * GnssRequest#isFullTrackingEnabled()} is true, GNSS chipset switches off duty
+ * GnssRequest#isFullTracking()} is true, GNSS chipset switches off duty
* cycling.
* @param executor the executor that the callback runs on.
* @param callback a {@link GnssMeasurementsEvent.Callback} object to register.
@@ -2216,7 +2214,12 @@ public class LocationManager {
@NonNull GnssRequest request,
@NonNull @CallbackExecutor Executor executor,
@NonNull GnssMeasurementsEvent.Callback callback) {
- throw new RuntimeException();
+ Preconditions.checkArgument(request != null, "invalid null request");
+ try {
+ return mGnssMeasurementsListenerManager.addListener(request, callback, executor);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -2763,8 +2766,7 @@ public class LocationManager {
}
private class GnssStatusListenerManager extends
- AbstractListenerManager<GnssStatus.Callback> {
-
+ AbstractListenerManager<Void, GnssStatus.Callback> {
@Nullable
private IGnssStatusListener mListenerTransport;
@@ -2782,19 +2784,19 @@ public class LocationManager {
public boolean addListener(@NonNull GpsStatus.Listener listener, @NonNull Executor executor)
throws RemoteException {
- return addInternal(listener, executor);
+ return addInternal(null, listener, executor);
}
public boolean addListener(@NonNull OnNmeaMessageListener listener,
@NonNull Handler handler)
throws RemoteException {
- return addInternal(listener, handler);
+ return addInternal(null, listener, handler);
}
public boolean addListener(@NonNull OnNmeaMessageListener listener,
@NonNull Executor executor)
throws RemoteException {
- return addInternal(listener, executor);
+ return addInternal(null, listener, executor);
}
@Override
@@ -2833,7 +2835,7 @@ public class LocationManager {
}
@Override
- protected boolean registerService() throws RemoteException {
+ protected boolean registerService(Void ignored) throws RemoteException {
Preconditions.checkState(mListenerTransport == null);
GnssStatusListener transport = new GnssStatusListener();
@@ -2893,17 +2895,17 @@ public class LocationManager {
}
private class GnssMeasurementsListenerManager extends
- AbstractListenerManager<GnssMeasurementsEvent.Callback> {
+ AbstractListenerManager<GnssRequest, GnssMeasurementsEvent.Callback> {
@Nullable
private IGnssMeasurementsListener mListenerTransport;
@Override
- protected boolean registerService() throws RemoteException {
+ protected boolean registerService(GnssRequest request) throws RemoteException {
Preconditions.checkState(mListenerTransport == null);
GnssMeasurementsListener transport = new GnssMeasurementsListener();
- if (mService.addGnssMeasurementsListener(transport, mContext.getPackageName(),
+ if (mService.addGnssMeasurementsListener(request, transport, mContext.getPackageName(),
mContext.getFeatureId(), "gnss measurement callback")) {
mListenerTransport = transport;
return true;
@@ -2920,6 +2922,18 @@ public class LocationManager {
mListenerTransport = null;
}
+ @Override
+ @Nullable
+ protected GnssRequest merge(@NonNull GnssRequest[] requests) {
+ Preconditions.checkArgument(requests.length > 0);
+ for (GnssRequest request : requests) {
+ if (request.isFullTracking()) {
+ return request;
+ }
+ }
+ return requests[0];
+ }
+
private class GnssMeasurementsListener extends IGnssMeasurementsListener.Stub {
@Override
public void onGnssMeasurementsReceived(final GnssMeasurementsEvent event) {
@@ -2934,13 +2948,13 @@ public class LocationManager {
}
private class GnssNavigationMessageListenerManager extends
- AbstractListenerManager<GnssNavigationMessage.Callback> {
+ AbstractListenerManager<Void, GnssNavigationMessage.Callback> {
@Nullable
private IGnssNavigationMessageListener mListenerTransport;
@Override
- protected boolean registerService() throws RemoteException {
+ protected boolean registerService(Void ignored) throws RemoteException {
Preconditions.checkState(mListenerTransport == null);
GnssNavigationMessageListener transport = new GnssNavigationMessageListener();
@@ -2975,13 +2989,13 @@ public class LocationManager {
}
private class BatchedLocationCallbackManager extends
- AbstractListenerManager<BatchedLocationCallback> {
+ AbstractListenerManager<Void, BatchedLocationCallback> {
@Nullable
private IBatchedLocationCallback mListenerTransport;
@Override
- protected boolean registerService() throws RemoteException {
+ protected boolean registerService(Void ignored) throws RemoteException {
Preconditions.checkState(mListenerTransport == null);
BatchedLocationCallback transport = new BatchedLocationCallback();
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 3561f8393eea..9b183a3e0e92 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -244,7 +244,7 @@ public class Tuner implements AutoCloseable {
*
* <p>
* Tuner events are started when {@link #tune(FrontendSettings)} is called and end when {@link
- * #stopTune()} is called.
+ * #cancelTuning()} is called.
*
* @param eventListener receives tune events.
* @throws SecurityException if the caller does not have appropriate permissions.
@@ -309,7 +309,7 @@ public class Tuner implements AutoCloseable {
*/
@RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
@Result
- public int stopTune() {
+ public int cancelTuning() {
TunerUtils.checkTunerPermission(mContext);
return nativeStopTune();
}
@@ -322,8 +322,8 @@ public class Tuner implements AutoCloseable {
* @param settings A {@link FrontendSettings} to configure the frontend.
* @param scanType The scan type.
* @throws SecurityException if the caller does not have appropriate permissions.
- * @throws IllegalStateException if {@code scan} is called again before {@link #stopScan()} is
- * called.
+ * @throws IllegalStateException if {@code scan} is called again before
+ * {@link #cancelScanning()} is called.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
@Result
@@ -354,7 +354,7 @@ public class Tuner implements AutoCloseable {
*/
@RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
@Result
- public int stopScan() {
+ public int cancelScanning() {
TunerUtils.checkTunerPermission(mContext);
int retVal = nativeStopScan();
mScanCallback = null;
diff --git a/media/tests/TunerTest/OWNERS b/media/tests/TunerTest/OWNERS
new file mode 100644
index 000000000000..73ea663aa37e
--- /dev/null
+++ b/media/tests/TunerTest/OWNERS
@@ -0,0 +1,4 @@
+amyjojo@google.com
+nchalko@google.com
+quxiangfang@google.com
+shubang@google.com
diff --git a/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java b/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java
index bd5b79594c19..c4e41c87564f 100644
--- a/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java
+++ b/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java
@@ -16,6 +16,8 @@
package com.android.incremental.nativeadb;
+import android.annotation.NonNull;
+import android.content.pm.DataLoaderParams;
import android.service.dataloader.DataLoaderService;
/** This code is used for testing only. */
@@ -26,7 +28,7 @@ public class NativeAdbDataLoaderService extends DataLoaderService {
}
@Override
- public DataLoader onCreateDataLoader() {
+ public DataLoader onCreateDataLoader(@NonNull DataLoaderParams dataLoaderParams) {
return null;
}
}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 804e0cb021b7..59881e7ba13d 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1069,17 +1069,17 @@
<string name="power_remaining_only_more_than_subtext">More than <xliff:g id="time_remaining">%1$s</xliff:g> remaining</string>
<!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device -->
- <string name="power_remaining_duration_only_shutdown_imminent" product="default">Phone may shutdown soon</string>
+ <string name="power_remaining_duration_only_shutdown_imminent" product="default">Phone may shut down soon</string>
<!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device -->
- <string name="power_remaining_duration_only_shutdown_imminent" product="tablet">Tablet may shutdown soon</string>
+ <string name="power_remaining_duration_only_shutdown_imminent" product="tablet">Tablet may shut down soon</string>
<!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device -->
- <string name="power_remaining_duration_only_shutdown_imminent" product="device">Device may shutdown soon</string>
+ <string name="power_remaining_duration_only_shutdown_imminent" product="device">Device may shut down soon</string>
<!-- [CHAR_LIMIT=60] Label for battery level chart when shutdown is imminent-->
- <string name="power_remaining_duration_shutdown_imminent" product="default">Phone may shutdown soon (<xliff:g id="level">%1$s</xliff:g>)</string>
+ <string name="power_remaining_duration_shutdown_imminent" product="default">Phone may shut down soon (<xliff:g id="level">%1$s</xliff:g>)</string>
<!-- [CHAR_LIMIT=60] Label for battery level chart when shutdown is imminent-->
- <string name="power_remaining_duration_shutdown_imminent" product="tablet">Tablet may shutdown soon (<xliff:g id="level">%1$s</xliff:g>)</string>
+ <string name="power_remaining_duration_shutdown_imminent" product="tablet">Tablet may shut down soon (<xliff:g id="level">%1$s</xliff:g>)</string>
<!-- [CHAR_LIMIT=60] Label for battery level chart when shutdown is imminent-->
- <string name="power_remaining_duration_shutdown_imminent" product="device">Device may shutdown soon (<xliff:g id="level">%1$s</xliff:g>)</string>
+ <string name="power_remaining_duration_shutdown_imminent" product="device">Device may shut down soon (<xliff:g id="level">%1$s</xliff:g>)</string>
<!-- [CHAR_LIMIT=40] Label for battery level chart when charging -->
<string name="power_charging"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="state">%2$s</xliff:g></string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index 1ebe91736ba1..d03a747b743c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -216,12 +216,12 @@ public class A2dpProfile implements LocalBluetoothProfile {
}
public boolean supportsHighQualityAudio(BluetoothDevice device) {
- int support = mService.supportsOptionalCodecs(device);
+ int support = mService.isOptionalCodecsSupported(device);
return support == BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED;
}
public boolean isHighQualityAudioEnabled(BluetoothDevice device) {
- int enabled = mService.getOptionalCodecsEnabled(device);
+ int enabled = mService.isOptionalCodecsEnabled(device);
if (enabled != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN) {
return enabled == BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED;
} else if (getConnectionStatus(device) != BluetoothProfile.STATE_CONNECTED &&
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
index b9081f241a91..b725ba5b8748 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
@@ -16,12 +16,9 @@
package com.android.settingslib.media;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
-import android.text.TextUtils;
-import android.util.Log;
import com.android.settingslib.R;
import com.android.settingslib.bluetooth.BluetoothUtils;
@@ -62,46 +59,6 @@ public class InfoMediaDevice extends MediaDevice {
return MediaDeviceUtils.getId(mRouteInfo);
}
- @Override
- public void requestSetVolume(int volume) {
- mRouterManager.requestSetVolume(mRouteInfo, volume);
- }
-
- @Override
- public int getMaxVolume() {
- return mRouteInfo.getVolumeMax();
- }
-
- @Override
- public int getCurrentVolume() {
- return mRouteInfo.getVolume();
- }
-
- @Override
- public String getClientPackageName() {
- return mRouteInfo.getClientPackageName();
- }
-
- @Override
- public String getClientAppLabel() {
- final String packageName = mRouteInfo.getClientPackageName();
- if (TextUtils.isEmpty(packageName)) {
- Log.d(TAG, "Client package name is empty");
- return mContext.getResources().getString(R.string.unknown);
- }
- try {
- final PackageManager packageManager = mContext.getPackageManager();
- final String appLabel = packageManager.getApplicationLabel(
- packageManager.getApplicationInfo(packageName, 0)).toString();
- if (!TextUtils.isEmpty(appLabel)) {
- return appLabel;
- }
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "unable to find " + packageName);
- }
- return mContext.getResources().getString(R.string.unknown);
- }
-
public boolean isConnected() {
return true;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index 580e086973d6..33c3d7e039b3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -16,14 +16,18 @@
package com.android.settingslib.media;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
import android.text.TextUtils;
+import android.util.Log;
import androidx.annotation.IntDef;
import androidx.annotation.VisibleForTesting;
+import com.android.settingslib.R;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -113,7 +117,9 @@ public abstract class MediaDevice implements Comparable<MediaDevice> {
*
* @param volume is the new value.
*/
+
public void requestSetVolume(int volume) {
+ mRouterManager.requestSetVolume(mRouteInfo, volume);
}
/**
@@ -122,7 +128,7 @@ public abstract class MediaDevice implements Comparable<MediaDevice> {
* @return max volume.
*/
public int getMaxVolume() {
- return 100;
+ return mRouteInfo.getVolumeMax();
}
/**
@@ -131,7 +137,7 @@ public abstract class MediaDevice implements Comparable<MediaDevice> {
* @return current volume.
*/
public int getCurrentVolume() {
- return 0;
+ return mRouteInfo.getVolume();
}
/**
@@ -140,7 +146,7 @@ public abstract class MediaDevice implements Comparable<MediaDevice> {
* @return package name.
*/
public String getClientPackageName() {
- return null;
+ return mRouteInfo.getClientPackageName();
}
/**
@@ -149,7 +155,22 @@ public abstract class MediaDevice implements Comparable<MediaDevice> {
* @return application label.
*/
public String getClientAppLabel() {
- return null;
+ final String packageName = mRouteInfo.getClientPackageName();
+ if (TextUtils.isEmpty(packageName)) {
+ Log.d(TAG, "Client package name is empty");
+ return mContext.getResources().getString(R.string.unknown);
+ }
+ try {
+ final PackageManager packageManager = mContext.getPackageManager();
+ final String appLabel = packageManager.getApplicationLabel(
+ packageManager.getApplicationInfo(packageName, 0)).toString();
+ if (!TextUtils.isEmpty(appLabel)) {
+ return appLabel;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "unable to find " + packageName);
+ }
+ return mContext.getResources().getString(R.string.unknown);
}
/**
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
index c555cbec4bab..cb9092eba441 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
@@ -73,26 +73,26 @@ public class A2dpProfileTest {
@Test
public void supportsHighQualityAudio() {
- when(mBluetoothA2dp.supportsOptionalCodecs(any())).thenReturn(
+ when(mBluetoothA2dp.isOptionalCodecsSupported(any())).thenReturn(
BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED);
assertThat(mProfile.supportsHighQualityAudio(mDevice)).isTrue();
- when(mBluetoothA2dp.supportsOptionalCodecs(any())).thenReturn(
+ when(mBluetoothA2dp.isOptionalCodecsSupported(any())).thenReturn(
BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED);
assertThat(mProfile.supportsHighQualityAudio(mDevice)).isFalse();
- when(mBluetoothA2dp.supportsOptionalCodecs(any())).thenReturn(
+ when(mBluetoothA2dp.isOptionalCodecsSupported(any())).thenReturn(
BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN);
assertThat(mProfile.supportsHighQualityAudio(mDevice)).isFalse();
}
@Test
public void isHighQualityAudioEnabled() {
- when(mBluetoothA2dp.getOptionalCodecsEnabled(any())).thenReturn(
+ when(mBluetoothA2dp.isOptionalCodecsEnabled(any())).thenReturn(
BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED);
assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isTrue();
- when(mBluetoothA2dp.getOptionalCodecsEnabled(any())).thenReturn(
+ when(mBluetoothA2dp.isOptionalCodecsEnabled(any())).thenReturn(
BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED);
assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isFalse();
@@ -100,16 +100,16 @@ public class A2dpProfileTest {
// then isHighQualityAudioEnabled() should return true or false based on whether optional
// codecs are supported. If the device is connected then we should ask it directly, but if
// the device isn't connected then rely on the stored pref about such support.
- when(mBluetoothA2dp.getOptionalCodecsEnabled(any())).thenReturn(
+ when(mBluetoothA2dp.isOptionalCodecsEnabled(any())).thenReturn(
BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN);
when(mBluetoothA2dp.getConnectionState(any())).thenReturn(
BluetoothProfile.STATE_DISCONNECTED);
- when(mBluetoothA2dp.supportsOptionalCodecs(any())).thenReturn(
+ when(mBluetoothA2dp.isOptionalCodecsSupported(any())).thenReturn(
BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED);
assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isFalse();
- when(mBluetoothA2dp.supportsOptionalCodecs(any())).thenReturn(
+ when(mBluetoothA2dp.isOptionalCodecsSupported(any())).thenReturn(
BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED);
assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isTrue();
@@ -151,14 +151,14 @@ public class A2dpProfileTest {
// Most tests want to simulate optional codecs being supported by the device, so do that
// by default here.
- when(mBluetoothA2dp.supportsOptionalCodecs(any())).thenReturn(
+ when(mBluetoothA2dp.isOptionalCodecsSupported(any())).thenReturn(
BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED);
}
@Test
public void getLableCodecsNotSupported() {
setupLabelTest();
- when(mBluetoothA2dp.supportsOptionalCodecs(any())).thenReturn(
+ when(mBluetoothA2dp.isOptionalCodecsSupported(any())).thenReturn(
BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED);
assertThat(mProfile.getHighQualityAudioOptionLabel(mDevice)).isEqualTo(UNKNOWN_CODEC_LABEL);
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java
index 04ceb2147c6e..77a67c286989 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java
@@ -21,9 +21,6 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.when;
import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageStats;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
@@ -36,14 +33,11 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
-import org.robolectric.Shadows;
-import org.robolectric.shadows.ShadowPackageManager;
@RunWith(RobolectricTestRunner.class)
public class InfoMediaDeviceTest {
private static final String TEST_PACKAGE_NAME = "com.test.packagename";
- private static final String TEST_PACKAGE_NAME2 = "com.test.packagename2";
private static final String TEST_ID = "test_id";
private static final String TEST_NAME = "test_name";
@@ -52,27 +46,13 @@ public class InfoMediaDeviceTest {
@Mock
private MediaRoute2Info mRouteInfo;
-
private Context mContext;
private InfoMediaDevice mInfoMediaDevice;
- private ShadowPackageManager mShadowPackageManager;
- private ApplicationInfo mAppInfo;
- private PackageInfo mPackageInfo;
- private PackageStats mPackageStats;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
- mShadowPackageManager = Shadows.shadowOf(mContext.getPackageManager());
- mAppInfo = new ApplicationInfo();
- mAppInfo.flags = ApplicationInfo.FLAG_INSTALLED;
- mAppInfo.packageName = TEST_PACKAGE_NAME;
- mAppInfo.name = TEST_NAME;
- mPackageInfo = new PackageInfo();
- mPackageInfo.packageName = TEST_PACKAGE_NAME;
- mPackageInfo.applicationInfo = mAppInfo;
- mPackageStats = new PackageStats(TEST_PACKAGE_NAME);
mInfoMediaDevice = new InfoMediaDevice(mContext, mRouterManager, mRouteInfo,
TEST_PACKAGE_NAME);
@@ -106,32 +86,4 @@ public class InfoMediaDeviceTest {
assertThat(mInfoMediaDevice.getId()).isEqualTo(TEST_ID);
}
-
- @Test
- public void getClientPackageName_returnPackageName() {
- when(mRouteInfo.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
-
- assertThat(mInfoMediaDevice.getClientPackageName()).isEqualTo(TEST_PACKAGE_NAME);
- }
-
- @Test
- public void getClientAppLabel_matchedPackageName_returnLabel() {
- when(mRouteInfo.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
-
- assertThat(mInfoMediaDevice.getClientAppLabel()).isEqualTo(
- mContext.getResources().getString(R.string.unknown));
-
- mShadowPackageManager.addPackage(mPackageInfo, mPackageStats);
-
- assertThat(mInfoMediaDevice.getClientAppLabel()).isEqualTo(TEST_NAME);
- }
-
- @Test
- public void getClientAppLabel_noMatchedPackageName_returnDefault() {
- mShadowPackageManager.addPackage(mPackageInfo, mPackageStats);
- when(mRouteInfo.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME2);
-
- assertThat(mInfoMediaDevice.getClientAppLabel()).isEqualTo(
- mContext.getResources().getString(R.string.unknown));
- }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
index fb8b78b22055..3f29b72b978d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
@@ -23,9 +23,13 @@ import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageStats;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
+import com.android.settingslib.R;
import com.android.settingslib.bluetooth.A2dpProfile;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.HearingAidProfile;
@@ -39,6 +43,8 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.shadows.ShadowPackageManager;
import java.util.ArrayList;
import java.util.Collections;
@@ -58,6 +64,8 @@ public class MediaDeviceTest {
private static final String ROUTER_ID_2 = "RouterId_2";
private static final String ROUTER_ID_3 = "RouterId_3";
private static final String TEST_PACKAGE_NAME = "com.test.playmusic";
+ private static final String TEST_PACKAGE_NAME2 = "com.test.playmusic2";
+ private static final String TEST_APPLICATION_LABEL = "playmusic";
private final BluetoothClass mHeadreeClass =
new BluetoothClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
private final BluetoothClass mCarkitClass =
@@ -111,6 +119,10 @@ public class MediaDeviceTest {
private InfoMediaDevice mInfoMediaDevice3;
private List<MediaDevice> mMediaDevices = new ArrayList<>();
private PhoneMediaDevice mPhoneMediaDevice;
+ private ShadowPackageManager mShadowPackageManager;
+ private ApplicationInfo mAppInfo;
+ private PackageInfo mPackageInfo;
+ private PackageStats mPackageStats;
@Before
public void setUp() {
@@ -394,4 +406,46 @@ public class MediaDeviceTest {
verify(mMediaRouter2Manager).selectRoute(TEST_PACKAGE_NAME, mRouteInfo1);
}
+
+ @Test
+ public void getClientPackageName_returnPackageName() {
+ when(mRouteInfo1.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+
+ assertThat(mInfoMediaDevice1.getClientPackageName()).isEqualTo(TEST_PACKAGE_NAME);
+ }
+
+ private void initPackage() {
+ mShadowPackageManager = Shadows.shadowOf(mContext.getPackageManager());
+ mAppInfo = new ApplicationInfo();
+ mAppInfo.flags = ApplicationInfo.FLAG_INSTALLED;
+ mAppInfo.packageName = TEST_PACKAGE_NAME;
+ mAppInfo.name = TEST_APPLICATION_LABEL;
+ mPackageInfo = new PackageInfo();
+ mPackageInfo.packageName = TEST_PACKAGE_NAME;
+ mPackageInfo.applicationInfo = mAppInfo;
+ mPackageStats = new PackageStats(TEST_PACKAGE_NAME);
+ }
+
+ @Test
+ public void getClientAppLabel_matchedPackageName_returnLabel() {
+ initPackage();
+ when(mRouteInfo1.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+
+ assertThat(mInfoMediaDevice1.getClientAppLabel()).isEqualTo(
+ mContext.getResources().getString(R.string.unknown));
+
+ mShadowPackageManager.addPackage(mPackageInfo, mPackageStats);
+
+ assertThat(mInfoMediaDevice1.getClientAppLabel()).isEqualTo(TEST_APPLICATION_LABEL);
+ }
+
+ @Test
+ public void getClientAppLabel_noMatchedPackageName_returnDefault() {
+ initPackage();
+ mShadowPackageManager.addPackage(mPackageInfo, mPackageStats);
+ when(mRouteInfo1.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME2);
+
+ assertThat(mInfoMediaDevice1.getClientAppLabel()).isEqualTo(
+ mContext.getResources().getString(R.string.unknown));
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java
index 61fdbd54ead1..ed308c8d6f6a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java
@@ -120,9 +120,9 @@ public class PowerUtilTest {
true /* basedOnUsage */);
// additional battery percentage in this string
- assertThat(info).isEqualTo("Phone may shutdown soon (10%)");
+ assertThat(info).isEqualTo("Phone may shut down soon (10%)");
// shortened string should not have percentage
- assertThat(info2).isEqualTo("Phone may shutdown soon");
+ assertThat(info2).isEqualTo("Phone may shut down soon");
}
@Test
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index c969bfd193b5..7b518a6167b7 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2193,7 +2193,7 @@ public class SettingsProvider extends ContentProvider {
if (prefix == null) {
return;
}
- String callingPackage = getCallingPackage();
+ String callingPackage = resolveCallingPackage();
String namespace = prefix.replace("/", "");
if (DeviceConfig.getPublicNamespaces().contains(namespace)) {
return;
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 1fe967b4750d..5458676e1061 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -646,16 +646,17 @@
android:label="Controls Providers"
android:theme="@style/Theme.ControlsManagement"
android:showForAllUsers="true"
+ android:clearTaskOnLaunch="true"
android:excludeFromRecents="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
android:visibleToInstantApps="true">
</activity>
<activity android:name=".controls.management.ControlsFavoritingActivity"
- android:parentActivityName=".controls.management.ControlsProviderSelectorActivity"
android:theme="@style/Theme.ControlsManagement"
android:excludeFromRecents="true"
android:showForAllUsers="true"
+ android:finishOnTaskLaunch="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
android:visibleToInstantApps="true">
</activity>
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index 01811e9cdced..f607cc8f7e15 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -118,6 +118,7 @@ public interface QSTile {
public CharSequence label;
public CharSequence secondaryLabel;
public CharSequence contentDescription;
+ public CharSequence stateDescription;
public CharSequence dualLabelContentDescription;
public boolean disabledByPolicy;
public boolean dualTarget = false;
@@ -135,6 +136,7 @@ public interface QSTile {
|| !Objects.equals(other.label, label)
|| !Objects.equals(other.secondaryLabel, secondaryLabel)
|| !Objects.equals(other.contentDescription, contentDescription)
+ || !Objects.equals(other.stateDescription, stateDescription)
|| !Objects.equals(other.dualLabelContentDescription,
dualLabelContentDescription)
|| !Objects.equals(other.expandedAccessibilityClassName,
@@ -151,6 +153,7 @@ public interface QSTile {
other.label = label;
other.secondaryLabel = secondaryLabel;
other.contentDescription = contentDescription;
+ other.stateDescription = stateDescription;
other.dualLabelContentDescription = dualLabelContentDescription;
other.expandedAccessibilityClassName = expandedAccessibilityClassName;
other.disabledByPolicy = disabledByPolicy;
@@ -177,6 +180,7 @@ public interface QSTile {
sb.append(",label=").append(label);
sb.append(",secondaryLabel=").append(secondaryLabel);
sb.append(",contentDescription=").append(contentDescription);
+ sb.append(",stateDescription=").append(stateDescription);
sb.append(",dualLabelContentDescription=").append(dualLabelContentDescription);
sb.append(",expandedAccessibilityClassName=").append(expandedAccessibilityClassName);
sb.append(",disabledByPolicy=").append(disabledByPolicy);
diff --git a/packages/SystemUI/res-keyguard/layout/controls_management.xml b/packages/SystemUI/res-keyguard/layout/controls_management.xml
deleted file mode 100644
index 8330258e2456..000000000000
--- a/packages/SystemUI/res-keyguard/layout/controls_management.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center_horizontal"
- android:paddingTop="@dimen/controls_management_top_padding"
- android:paddingStart="@dimen/controls_management_side_padding"
- android:paddingEnd="@dimen/controls_management_side_padding" >
-
- <TextView
- android:id="@+id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:textSize="@dimen/controls_title_size"
- android:textAlignment="center" />
-
- <TextView
- android:id="@+id/subtitle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/controls_management_titles_margin"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:textAlignment="center" />
-
- <androidx.recyclerview.widget.RecyclerView
- android:id="@+id/list"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/controls_management_list_margin" />
-
-</LinearLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/controls_zone_header.xml b/packages/SystemUI/res-keyguard/layout/controls_zone_header.xml
new file mode 100644
index 000000000000..7b43a0315c10
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/controls_zone_header.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.Control.Title"
+ android:textColor="?android:attr/colorPrimary"
+ android:layout_marginStart="12dp"
+ android:layout_marginEnd="2dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginBottom="4dp">
+
+</TextView> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_cancel_24.xml b/packages/SystemUI/res/drawable/ic_cancel_24.xml
new file mode 100644
index 000000000000..8ab28ddb680d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_cancel_24.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM17,15.59L15.59,17 12,13.41 8.41,17 7,15.59 10.59,12 7,8.41 8.41,7 12,10.59 15.59,7 17,8.41 13.41,12 17,15.59z"/>
+</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_important.xml b/packages/SystemUI/res/drawable/ic_important.xml
new file mode 100644
index 000000000000..d7439e167dd4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_important.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M4,18.99h11c0.67,0 1.27,-0.32 1.63,-0.83L21,12l-4.37,-6.16C16.27,5.33 15.67,5 15,5H4l5,7 -5,6.99z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_important_outline.xml b/packages/SystemUI/res/drawable/ic_important_outline.xml
new file mode 100644
index 000000000000..7a628bb65433
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_important_outline.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M15,19L3,19l4.5,-7L3,5h12c0.65,0 1.26,0.31 1.63,0.84L21,12l-4.37,6.16c-0.37,0.52 -0.98,0.84 -1.63,0.84zM6.5,17L15,17l3.5,-5L15,7L6.5,7l3.5,5 -3.5,5z"/>
+</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_star.xml b/packages/SystemUI/res/drawable/ic_star.xml
deleted file mode 100644
index 4a731b35b423..000000000000
--- a/packages/SystemUI/res/drawable/ic_star.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
- Copyright (C) 2020 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License
- -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="@android:color/white"
- android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27z"/>
-</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_star_border.xml b/packages/SystemUI/res/drawable/ic_star_border.xml
deleted file mode 100644
index 9ede40be3b7b..000000000000
--- a/packages/SystemUI/res/drawable/ic_star_border.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
- Copyright (C) 2020 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License
- -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="@android:color/white"
- android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/>
-</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/screenshot_cancel.xml b/packages/SystemUI/res/drawable/screenshot_cancel.xml
new file mode 100644
index 000000000000..be3c5983bb2e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/screenshot_cancel.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0">
+ <path
+ android:pathData="M24,24m-16,0a16,16 0,1 1,32 0a16,16 0,1 1,-32 0"
+ android:fillColor="@android:color/white"/>
+ <path
+ android:fillColor="@color/GM2_grey_500"
+ android:pathData="M31,18.41L29.59,17 24,22.59 18.41,17 17,18.41 22.59,24 17,29.59 18.41,31 24,25.41 29.59,31 31,29.59 25.41,24z"/>
+</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/controls_base_item.xml b/packages/SystemUI/res/layout/controls_base_item.xml
index 68c824698b2d..823bbcd6e68c 100644
--- a/packages/SystemUI/res/layout/controls_base_item.xml
+++ b/packages/SystemUI/res/layout/controls_base_item.xml
@@ -72,8 +72,8 @@
<CheckBox
android:id="@+id/favorite"
android:visibility="gone"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/controls_management.xml b/packages/SystemUI/res/layout/controls_management.xml
new file mode 100644
index 000000000000..a7379bedebef
--- /dev/null
+++ b/packages/SystemUI/res/layout/controls_management.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_horizontal"
+ android:paddingTop="@dimen/controls_management_top_padding"
+ android:paddingStart="@dimen/controls_management_side_padding"
+ android:paddingEnd="@dimen/controls_management_side_padding" >
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textSize="@dimen/controls_title_size"
+ android:textAlignment="center" />
+
+ <TextView
+ android:id="@+id/subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/controls_management_titles_margin"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textAlignment="center" />
+
+ <androidx.core.widget.NestedScrollView
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:layout_marginTop="@dimen/controls_management_list_margin">
+
+ <ViewStub
+ android:id="@+id/stub"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
+ </androidx.core.widget.NestedScrollView>
+
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="64dp">
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/controls_app_divider_height"
+ android:layout_gravity="center_horizontal|top"
+ android:background="?android:attr/listDivider" />
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="4dp">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:text="See other apps"
+ android:textAppearance="@style/TextAppearance.Control.Title"
+ android:textColor="?android:attr/colorPrimary"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"/>
+
+ <Button
+ android:id="@+id/done"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:text="Done"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+ </androidx.constraintlayout.widget.ConstraintLayout>
+ </FrameLayout>
+
+
+</LinearLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/controls_management_apps.xml b/packages/SystemUI/res/layout/controls_management_apps.xml
new file mode 100644
index 000000000000..2bab433d21f3
--- /dev/null
+++ b/packages/SystemUI/res/layout/controls_management_apps.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<androidx.recyclerview.widget.RecyclerView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ >
+
+</androidx.recyclerview.widget.RecyclerView> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/controls_management_favorites.xml b/packages/SystemUI/res/layout/controls_management_favorites.xml
new file mode 100644
index 000000000000..a36dd1247a04
--- /dev/null
+++ b/packages/SystemUI/res/layout/controls_management_favorites.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/text_favorites"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="FAVORITES"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/divider1"
+ app:layout_constraintTop_toTopOf="parent"
+ />
+
+ <View
+ android:id="@+id/divider1"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/controls_app_divider_height"
+ android:layout_gravity="center_horizontal|top"
+ android:background="?android:attr/listDivider"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/listFavorites"
+ app:layout_constraintTop_toBottomOf="@id/text_favorites"
+ />
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/listFavorites"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginTop="@dimen/controls_management_list_margin"
+ android:nestedScrollingEnabled="false"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/text_all"
+ app:layout_constraintTop_toBottomOf="@id/divider1"/>
+
+ <TextView
+ android:id="@+id/text_all"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/controls_management_list_margin"
+ android:text="ALL"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/divider2"
+ app:layout_constraintTop_toBottomOf="@id/listFavorites"
+ />
+
+ <View
+ android:id="@+id/divider2"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/controls_app_divider_height"
+ android:layout_gravity="center_horizontal|top"
+ android:background="?android:attr/listDivider"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/listAll"
+ app:layout_constraintTop_toBottomOf="@id/text_all"
+ />
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/listAll"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginTop="@dimen/controls_management_list_margin"
+ android:nestedScrollingEnabled="false"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/divider2"/>
+
+</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml
index 40b2476941c2..2cd9505b8fe4 100644
--- a/packages/SystemUI/res/layout/controls_with_favorites.xml
+++ b/packages/SystemUI/res/layout/controls_with_favorites.xml
@@ -1,3 +1,18 @@
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml
index 1f7def2bc956..d0151fff95c4 100644
--- a/packages/SystemUI/res/layout/global_screenshot.xml
+++ b/packages/SystemUI/res/layout/global_screenshot.xml
@@ -55,10 +55,22 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
- android:elevation="8dp"
+ android:elevation="@dimen/screenshot_preview_elevation"
android:visibility="gone"
android:background="@drawable/screenshot_rounded_corners"
android:adjustViewBounds="true"/>
+ <FrameLayout
+ android:id="@+id/global_screenshot_dismiss_button"
+ android:layout_width="@dimen/screenshot_dismiss_button_tappable_size"
+ android:layout_height="@dimen/screenshot_dismiss_button_tappable_size"
+ android:elevation="9dp"
+ android:visibility="gone">
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="@dimen/screenshot_dismiss_button_margin"
+ android:src="@drawable/screenshot_cancel"/>
+ </FrameLayout>
<ImageView
android:id="@+id/global_screenshot_flash"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml
index a9d6e3575317..84606126086d 100644
--- a/packages/SystemUI/res/layout/notification_conversation_info.xml
+++ b/packages/SystemUI/res/layout/notification_conversation_info.xml
@@ -28,7 +28,7 @@
android:paddingStart="@*android:dimen/notification_content_margin_start">
<!-- Package Info -->
- <RelativeLayout
+ <LinearLayout
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="@dimen/notification_guts_conversation_header_height"
@@ -41,16 +41,20 @@
android:layout_height="@dimen/notification_guts_conversation_icon_size"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
- android:layout_marginEnd="6dp" />
+ android:layout_marginEnd="15dp" />
<LinearLayout
android:id="@+id/names"
+ android:layout_weight="1"
+ android:layout_width="0dp"
android:orientation="vertical"
- android:layout_width="wrap_content"
+
android:layout_height="wrap_content"
android:minHeight="@dimen/notification_guts_conversation_icon_size"
android:layout_centerVertical="true"
android:gravity="center_vertical"
- android:layout_toEndOf="@id/conversation_icon">
+ android:layout_alignEnd="@id/conversation_icon"
+ android:layout_toEndOf="@id/conversation_icon"
+ android:layout_alignStart="@id/mute">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -107,67 +111,40 @@
android:layout_weight="1"
style="@style/TextAppearance.NotificationImportanceChannel"/>
</LinearLayout>
+ <TextView
+ android:id="@+id/delegate_name"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ style="@style/TextAppearance.NotificationImportanceHeader"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:ellipsize="end"
+ android:text="@string/notification_delegate_header"
+ android:layout_toEndOf="@id/pkg_divider"
+ android:maxLines="1" />
</LinearLayout>
- <TextView
- android:id="@+id/pkg_divider"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerVertical="true"
- style="@style/TextAppearance.NotificationImportanceHeader"
- android:layout_marginStart="2dp"
- android:layout_marginEnd="2dp"
- android:layout_toEndOf="@id/name"
- android:text="@*android:string/notification_header_divider_symbol" />
- <TextView
- android:id="@+id/delegate_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerVertical="true"
- style="@style/TextAppearance.NotificationImportanceHeader"
- android:layout_marginStart="2dp"
- android:layout_marginEnd="2dp"
- android:ellipsize="end"
- android:text="@string/notification_delegate_header"
- android:layout_toEndOf="@id/pkg_divider"
- android:maxLines="1" />
-
<!-- end aligned fields -->
<ImageButton
- android:id="@+id/demote"
- android:layout_width="@dimen/notification_importance_toggle_size"
- android:layout_height="@dimen/notification_importance_toggle_size"
- android:layout_centerVertical="true"
- android:background="@drawable/ripple_drawable"
- android:contentDescription="@string/demote"
- android:src="@drawable/ic_demote_conversation"
- android:layout_toStartOf="@id/app_settings"
- android:tint="@color/notification_guts_link_icon_tint"/>
- <!-- Optional link to app. Only appears if the channel is not disabled and the app
-asked for it -->
- <ImageButton
- android:id="@+id/app_settings"
+ android:id="@+id/mute"
android:layout_width="@dimen/notification_importance_toggle_size"
android:layout_height="@dimen/notification_importance_toggle_size"
android:layout_centerVertical="true"
- android:visibility="gone"
android:background="@drawable/ripple_drawable"
- android:contentDescription="@string/notification_app_settings"
- android:src="@drawable/ic_info"
- android:layout_toStartOf="@id/info"
+ android:layout_toStartOf="@id/fave"
android:tint="@color/notification_guts_link_icon_tint"/>
<ImageButton
- android:id="@+id/info"
+ android:id="@+id/fave"
android:layout_width="@dimen/notification_importance_toggle_size"
android:layout_height="@dimen/notification_importance_toggle_size"
android:layout_centerVertical="true"
android:background="@drawable/ripple_drawable"
- android:contentDescription="@string/notification_more_settings"
- android:src="@drawable/ic_settings"
android:layout_alignParentEnd="true"
android:tint="@color/notification_guts_link_icon_tint"/>
- </RelativeLayout>
+
+ </LinearLayout>
<LinearLayout
android:id="@+id/actions"
@@ -182,29 +159,15 @@ asked for it -->
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="@color/GM2_grey_300" />
- <Button
- android:id="@+id/bubble"
- android:layout_height="@dimen/notification_guts_conversation_action_height"
- android:layout_width="match_parent"
- style="?android:attr/borderlessButtonStyle"
- android:text="@string/notification_conversation_favorite"
- android:gravity="left|center_vertical"
- android:drawableStart="@drawable/ic_create_bubble"
- android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
- android:drawableTint="@color/notification_guts_link_icon_tint"/>
- <View
- android:layout_width="match_parent"
- android:layout_height="0.5dp"
- android:background="@color/GM2_grey_300" />
<Button
- android:id="@+id/home"
+ android:id="@+id/snooze"
android:layout_height="@dimen/notification_guts_conversation_action_height"
android:layout_width="match_parent"
style="?android:attr/borderlessButtonStyle"
- android:text="@string/notification_conversation_home_screen"
+ android:text="@string/notification_menu_snooze_action"
android:gravity="left|center_vertical"
- android:drawableStart="@drawable/ic_add_to_home"
+ android:drawableStart="@drawable/ic_snooze"
android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
android:drawableTint="@color/notification_guts_link_icon_tint"/>
@@ -212,12 +175,15 @@ asked for it -->
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="@color/GM2_grey_300" />
+
<Button
- android:id="@+id/fave"
+ android:id="@+id/bubble"
android:layout_height="@dimen/notification_guts_conversation_action_height"
android:layout_width="match_parent"
style="?android:attr/borderlessButtonStyle"
+ android:text="@string/notification_conversation_favorite"
android:gravity="left|center_vertical"
+ android:drawableStart="@drawable/ic_create_bubble"
android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
android:drawableTint="@color/notification_guts_link_icon_tint"/>
@@ -226,13 +192,13 @@ asked for it -->
android:layout_height="0.5dp"
android:background="@color/GM2_grey_300" />
<Button
- android:id="@+id/snooze"
+ android:id="@+id/home"
android:layout_height="@dimen/notification_guts_conversation_action_height"
android:layout_width="match_parent"
style="?android:attr/borderlessButtonStyle"
- android:text="@string/notification_menu_snooze_action"
+ android:text="@string/notification_conversation_home_screen"
android:gravity="left|center_vertical"
- android:drawableStart="@drawable/ic_snooze"
+ android:drawableStart="@drawable/ic_add_to_home"
android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
android:drawableTint="@color/notification_guts_link_icon_tint"/>
@@ -240,14 +206,15 @@ asked for it -->
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="@color/GM2_grey_300" />
+
<Button
- android:id="@+id/mute"
+ android:id="@+id/info"
android:layout_height="@dimen/notification_guts_conversation_action_height"
android:layout_width="match_parent"
style="?android:attr/borderlessButtonStyle"
- android:text="@string/notification_conversation_mute"
+ android:drawableStart="@drawable/ic_settings"
+ android:text="@string/notification_menu_settings_action"
android:gravity="left|center_vertical"
- android:drawableStart="@drawable/ic_notifications_silence"
android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
android:drawableTint="@color/notification_guts_link_icon_tint"/>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 7a3395c229a0..f0e4e53dfc53 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -103,7 +103,8 @@
<item name="status_bar_icon_scale_factor" format="float" type="dimen">1.0</item>
<dimen name="group_overflow_number_size">@*android:dimen/notification_text_size</dimen>
- <dimen name="group_overflow_number_padding">@*android:dimen/notification_content_margin_end</dimen>
+ <dimen name="group_overflow_number_padding">@*android:dimen/notification_content_margin_end
+ </dimen>
<!-- max height of a notification such that the content can still fade out when closing -->
<dimen name="max_notification_fadeout_height">100dp</dimen>
@@ -267,7 +268,9 @@
<dimen name="status_bar_icon_drawing_size">15dp</dimen>
<!-- size at which Notification icons will be drawn on Ambient Display -->
- <dimen name="status_bar_icon_drawing_size_dark">@*android:dimen/notification_header_icon_size_ambient</dimen>
+ <dimen name="status_bar_icon_drawing_size_dark">
+ @*android:dimen/notification_header_icon_size_ambient
+ </dimen>
<!-- size of notification icons when the notifications are hidden -->
<dimen name="hidden_shelf_icon_size">16dp</dimen>
@@ -299,8 +302,11 @@
<dimen name="global_screenshot_legacy_bg_padding">20dp</dimen>
<dimen name="global_screenshot_bg_padding">20dp</dimen>
<dimen name="global_screenshot_x_scale">80dp</dimen>
+ <dimen name="screenshot_preview_elevation">8dp</dimen>
<dimen name="screenshot_offset_y">48dp</dimen>
<dimen name="screenshot_offset_x">16dp</dimen>
+ <dimen name="screenshot_dismiss_button_tappable_size">48dp</dimen>
+ <dimen name="screenshot_dismiss_button_margin">8dp</dimen>
<dimen name="screenshot_action_container_offset_y">32dp</dimen>
<dimen name="screenshot_action_container_corner_radius">10dp</dimen>
<dimen name="screenshot_action_container_padding_vertical">10dp</dimen>
@@ -597,10 +603,14 @@
<!-- The height of the divider between the individual notifications in a notification
group. -->
- <dimen name="notification_children_container_divider_height">@dimen/notification_divider_height</dimen>
+ <dimen name="notification_children_container_divider_height">
+ @dimen/notification_divider_height
+ </dimen>
<!-- The top margin for the notification children container in its non-expanded form. -->
- <dimen name="notification_children_container_margin_top">@*android:dimen/notification_content_margin_top</dimen>
+ <dimen name="notification_children_container_margin_top">
+ @*android:dimen/notification_content_margin_top
+ </dimen>
<!-- The height of a notification header -->
<dimen name="notification_header_height">53dp</dimen>
@@ -967,6 +977,9 @@
Equal to pip_action_size - pip_action_padding. -->
<dimen name="pip_expand_container_edge_margin">30dp</dimen>
+ <!-- The touchable/draggable edge size for PIP resize. -->
+ <dimen name="pip_resize_edge_size">30dp</dimen>
+
<dimen name="default_gear_space">18dp</dimen>
<dimen name="cell_overlay_padding">18dp</dimen>
@@ -1061,9 +1074,12 @@
<integer name="wireless_charging_scale_dots_duration">83</integer>
<integer name="wireless_charging_num_dots">16</integer>
<!-- Starting text size in sp of batteryLevel for wireless charging animation -->
- <item name="wireless_charging_anim_battery_level_text_size_start" format="float" type="dimen">0</item>
+ <item name="wireless_charging_anim_battery_level_text_size_start" format="float" type="dimen">
+ 0
+ </item>
<!-- Ending text size in sp of batteryLevel for wireless charging animation -->
- <item name="wireless_charging_anim_battery_level_text_size_end" format="float" type="dimen">24</item>
+ <item name="wireless_charging_anim_battery_level_text_size_end" format="float" type="dimen">24
+ </item>
<!-- time until battery info is at full opacity-->
<integer name="wireless_charging_anim_opacity_offset">80</integer>
<!-- duration batteryLevel opacity goes from 0 to 1 duration -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b85b51e4f2cc..8f9e934c97e5 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1829,23 +1829,23 @@
<!-- Notification: Conversation: control panel, label for button that demotes notification from conversation to normal notification -->
<string name="demote">Mark this notification as not a conversation</string>
- <!-- [CHAR LIMIT=100] Mark this conversation as a favorite -->
- <string name="notification_conversation_favorite">Mark as important</string>
+ <!-- [CHAR LIMIT=100] This conversation is marked as important -->
+ <string name="notification_conversation_favorite">Important conversation</string>
- <!-- [CHAR LIMIT=100] Unmark this conversation as a favorite -->
- <string name="notification_conversation_unfavorite">Mark as unimportant</string>
+ <!-- [CHAR LIMIT=100] This conversation is not marked as important -->
+ <string name="notification_conversation_unfavorite">Not an important conversation</string>
- <!-- [CHAR LIMIT=100] Mute this conversation -->
- <string name="notification_conversation_mute">Silence</string>
+ <!-- [CHAR LIMIT=100] This conversation is silenced (will not make sound or vibrate)-->
+ <string name="notification_conversation_mute">Silenced</string>
- <!-- [CHAR LIMIT=100] Umute this conversation -->
+ <!-- [CHAR LIMIT=100] This conversation is alerting (may make sound and/or vibrate)-->
<string name="notification_conversation_unmute">Alerting</string>
<!-- [CHAR LIMIT=100] Show notification as bubble -->
- <string name="notification_conversation_bubble">Show as bubble</string>
+ <string name="notification_conversation_bubble">Show bubble</string>
<!-- [CHAR LIMIT=100] Turn off bubbles for notification -->
- <string name="notification_conversation_unbubble">Turn off bubbles</string>
+ <string name="notification_conversation_unbubble">Remove bubbles</string>
<!-- [CHAR LIMIT=100] Add this conversation to home screen -->
<string name="notification_conversation_home_screen">Add to home screen</string>
@@ -1860,7 +1860,10 @@
<string name="notification_menu_snooze_description">notification snooze options</string>
<!-- Notification: Menu row: Label for the snooze action shown in local context menu. [CHAR LIMIT=NONE] -->
- <string name="notification_menu_snooze_action">Snooze</string>
+ <string name="notification_menu_snooze_action">Remind me</string>
+
+ <!-- Notification: Menu row: Label for the snooze action shown in local context menu. [CHAR LIMIT=NONE] -->
+ <string name="notification_menu_settings_action">Settings</string>
<!-- Notification: Snooze panel: Snooze undo button label. [CHAR LIMIT=50]-->
<string name="snooze_undo">UNDO</string>
@@ -2029,6 +2032,9 @@
<!-- Label for feature switch [CHAR LIMIT=30] -->
<string name="switch_bar_off">Off</string>
+ <!-- The tile in quick settings is unavailable. [CHAR LIMIT=32] -->
+ <string name="tile_unavailable">Unavailable</string>
+
<!-- SysUI Tuner: Button that leads to the navigation bar customization screen [CHAR LIMIT=60] -->
<string name="nav_bar">Navigation bar</string>
@@ -2583,6 +2589,4 @@
<string name="controls_favorite_default_title">Controls</string>
<!-- Controls management controls screen subtitle [CHAR LIMIT=NONE] -->
<string name="controls_favorite_subtitle">Choose controls for quick access</string>
-
-
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
index bea55c820b40..2d55a1ddf654 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
@@ -146,7 +146,6 @@ class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.V
int viewType) {
BadgedImageView view = (BadgedImageView) LayoutInflater.from(parent.getContext())
.inflate(R.layout.bubble_view, parent, false);
- view.setPadding(15, 15, 15, 15);
return new ViewHolder(view);
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt
index 53841e2f144b..49a16d892ef4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt
@@ -20,6 +20,6 @@ import android.service.controls.Control
data class ControlStatus(
val control: Control,
- val favorite: Boolean,
+ var favorite: Boolean,
val removed: Boolean = false
) \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
index b3ba2b22f6df..fce504120b62 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
@@ -26,10 +26,18 @@ interface ControlsController : UserAwareController {
val available: Boolean
fun getFavoriteControls(): List<ControlInfo>
- fun loadForComponent(componentName: ComponentName, callback: (List<ControlStatus>) -> Unit)
+ fun loadForComponent(
+ componentName: ComponentName,
+ callback: (List<ControlStatus>, List<String>) -> Unit
+ )
+
fun subscribeToFavorites()
fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean)
- fun countFavoritesForComponent(componentName: ComponentName): Int = 0
+ fun replaceFavoritesForComponent(componentName: ComponentName, favorites: List<ControlInfo>)
+
+ fun getFavoritesForComponent(componentName: ComponentName): List<ControlInfo>
+ fun countFavoritesForComponent(componentName: ComponentName): Int
+
fun unsubscribe()
fun action(controlInfo: ControlInfo, action: ControlAction)
fun refreshStatus(componentName: ComponentName, control: Control)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index 7de1557ebc65..e611197b78ae 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -70,9 +70,10 @@ class ControlsControllerImpl @Inject constructor (
}
// Map of map: ComponentName -> (String -> ControlInfo).
- // Only for current user
+ //
@GuardedBy("currentFavorites")
- private val currentFavorites = ArrayMap<ComponentName, MutableMap<String, ControlInfo>>()
+ private val currentFavorites = ArrayMap<ComponentName, MutableList<ControlInfo>>()
+ .withDefault { mutableListOf() }
private var userChanging: Boolean = true
@@ -180,15 +181,14 @@ class ControlsControllerImpl @Inject constructor (
val infos = persistenceWrapper.readFavorites()
synchronized(currentFavorites) {
infos.forEach {
- currentFavorites.getOrPut(it.component, { ArrayMap<String, ControlInfo>() })
- .put(it.controlId, it)
+ currentFavorites.getOrPut(it.component, { mutableListOf() }).add(it)
}
}
}
override fun loadForComponent(
componentName: ComponentName,
- callback: (List<ControlStatus>) -> Unit
+ callback: (List<ControlStatus>, List<String>) -> Unit
) {
if (!confirmAvailability()) {
if (userChanging) {
@@ -200,29 +200,34 @@ class ControlsControllerImpl @Inject constructor (
TimeUnit.MILLISECONDS
)
} else {
- callback(emptyList())
+ callback(emptyList(), emptyList())
}
return
}
bindingController.bindAndLoad(componentName) {
synchronized(currentFavorites) {
- val favoritesForComponentKeys: Set<String> =
- currentFavorites.get(componentName)?.keys ?: emptySet()
- val changed = updateFavoritesLocked(componentName, it)
+ val favoritesForComponentKeys: List<String> =
+ currentFavorites.getValue(componentName).map { it.controlId }
+ val changed = updateFavoritesLocked(componentName, it, favoritesForComponentKeys)
if (changed) {
persistenceWrapper.storeFavorites(favoritesAsListLocked())
}
- val removed = findRemovedLocked(favoritesForComponentKeys, it)
- callback(removed.map { currentFavorites.getValue(componentName).getValue(it) }
- .map(::createRemovedStatus) +
- it.map { ControlStatus(it, it.controlId in favoritesForComponentKeys) })
+ val removed = findRemovedLocked(favoritesForComponentKeys.toSet(), it)
+ val controlsWithFavorite =
+ it.map { ControlStatus(it, it.controlId in favoritesForComponentKeys) }
+ callback(
+ currentFavorites.getValue(componentName)
+ .filter { it.controlId in removed }
+ .map(::createRemovedStatus) + controlsWithFavorite,
+ favoritesForComponentKeys
+ )
}
}
}
private fun createRemovedStatus(controlInfo: ControlInfo): ControlStatus {
val intent = Intent(context, ControlsFavoritingActivity::class.java).apply {
- putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, controlInfo.component)
+ putExtra(Intent.EXTRA_COMPONENT_NAME, controlInfo.component)
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
}
val pendingIntent = PendingIntent.getActivity(context,
@@ -243,17 +248,24 @@ class ControlsControllerImpl @Inject constructor (
}
@GuardedBy("currentFavorites")
- private fun updateFavoritesLocked(componentName: ComponentName, list: List<Control>): Boolean {
- val favorites = currentFavorites.get(componentName) ?: mutableMapOf()
- val favoriteKeys = favorites.keys
+ private fun updateFavoritesLocked(
+ componentName: ComponentName,
+ list: List<Control>,
+ favoriteKeys: List<String>
+ ): Boolean {
+ val favorites = currentFavorites.get(componentName) ?: mutableListOf()
if (favoriteKeys.isEmpty()) return false // early return
var changed = false
- list.forEach {
- if (it.controlId in favoriteKeys) {
- val value = favorites.getValue(it.controlId)
- if (value.controlTitle != it.title || value.deviceType != it.deviceType) {
- favorites[it.controlId] = value.copy(controlTitle = it.title,
- deviceType = it.deviceType)
+ list.forEach { control ->
+ if (control.controlId in favoriteKeys) {
+ val index = favorites.indexOfFirst { it.controlId == control.controlId }
+ val value = favorites[index]
+ if (value.controlTitle != control.title ||
+ value.deviceType != control.deviceType) {
+ favorites[index] = value.copy(
+ controlTitle = control.title,
+ deviceType = control.deviceType
+ )
changed = true
}
}
@@ -263,14 +275,14 @@ class ControlsControllerImpl @Inject constructor (
@GuardedBy("currentFavorites")
private fun favoritesAsListLocked(): List<ControlInfo> {
- return currentFavorites.flatMap { it.value.values }
+ return currentFavorites.flatMap { it.value }
}
override fun subscribeToFavorites() {
if (!confirmAvailability()) return
// Make a copy of the favorites list
val favorites = synchronized(currentFavorites) {
- currentFavorites.flatMap { it.value.values.toList() }
+ currentFavorites.flatMap { it.value }
}
bindingController.subscribe(favorites)
}
@@ -286,22 +298,19 @@ class ControlsControllerImpl @Inject constructor (
val listOfControls = synchronized(currentFavorites) {
if (state) {
if (controlInfo.component !in currentFavorites) {
- currentFavorites.put(controlInfo.component, ArrayMap<String, ControlInfo>())
+ currentFavorites.put(controlInfo.component, mutableListOf())
changed = true
}
val controlsForComponent = currentFavorites.getValue(controlInfo.component)
- if (controlInfo.controlId !in controlsForComponent) {
- controlsForComponent.put(controlInfo.controlId, controlInfo)
+ if (controlsForComponent.firstOrNull {
+ it.controlId == controlInfo.controlId
+ } == null) {
+ controlsForComponent.add(controlInfo)
changed = true
- } else {
- if (controlsForComponent.getValue(controlInfo.controlId) != controlInfo) {
- controlsForComponent.put(controlInfo.controlId, controlInfo)
- changed = true
- }
}
} else {
changed = currentFavorites.get(controlInfo.component)
- ?.remove(controlInfo.controlId) != null
+ ?.remove(controlInfo) != null
}
favoritesAsListLocked()
}
@@ -310,6 +319,19 @@ class ControlsControllerImpl @Inject constructor (
}
}
+ override fun replaceFavoritesForComponent(
+ componentName: ComponentName,
+ favorites: List<ControlInfo>
+ ) {
+ if (!confirmAvailability()) return
+ val filtered = favorites.filter { it.component == componentName }
+ val listOfControls = synchronized(currentFavorites) {
+ currentFavorites.put(componentName, filtered.toMutableList())
+ favoritesAsListLocked()
+ }
+ persistenceWrapper.storeFavorites(listOfControls)
+ }
+
override fun refreshStatus(componentName: ComponentName, control: Control) {
if (!confirmAvailability()) {
Log.d(TAG, "Controls not available")
@@ -317,7 +339,13 @@ class ControlsControllerImpl @Inject constructor (
}
executor.execute {
synchronized(currentFavorites) {
- val changed = updateFavoritesLocked(componentName, listOf(control))
+ val favoriteKeysForComponent =
+ currentFavorites.get(componentName)?.map { it.controlId } ?: emptyList()
+ val changed = updateFavoritesLocked(
+ componentName,
+ listOf(control),
+ favoriteKeysForComponent
+ )
if (changed) {
persistenceWrapper.storeFavorites(favoritesAsListLocked())
}
@@ -361,6 +389,12 @@ class ControlsControllerImpl @Inject constructor (
}
}
+ override fun getFavoritesForComponent(componentName: ComponentName): List<ControlInfo> {
+ return synchronized(currentFavorites) {
+ currentFavorites.get(componentName) ?: emptyList()
+ }
+ }
+
override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
pw.println("ControlsController state:")
pw.println(" Available: $available")
@@ -368,10 +402,8 @@ class ControlsControllerImpl @Inject constructor (
pw.println(" Current user: ${currentUser.identifier}")
pw.println(" Favorites:")
synchronized(currentFavorites) {
- currentFavorites.forEach {
- it.value.forEach {
- pw.println(" ${it.value}")
- }
+ favoritesAsListLocked().forEach {
+ pw.println(" ${ it }")
}
}
pw.println(bindingController.toString())
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
index b12243964fc1..89caaceebb5c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
@@ -29,6 +29,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.android.settingslib.applications.DefaultAppInfo
import com.android.settingslib.widget.CandidateInfo
import com.android.systemui.R
+import java.text.Collator
import java.util.concurrent.Executor
/**
@@ -44,23 +45,27 @@ import java.util.concurrent.Executor
* @param onAppSelected a callback to indicate that an app has been selected in the list.
*/
class AppAdapter(
+ backgroundExecutor: Executor,
uiExecutor: Executor,
lifecycle: Lifecycle,
controlsListingController: ControlsListingController,
private val layoutInflater: LayoutInflater,
private val onAppSelected: (ComponentName?) -> Unit = {},
- private val favoritesRenderer: FavoritesRenderer
+ private val favoritesRenderer: FavoritesRenderer,
+ private val resources: Resources
) : RecyclerView.Adapter<AppAdapter.Holder>() {
private var listOfServices = emptyList<CandidateInfo>()
private val callback = object : ControlsListingController.ControlsListingCallback {
override fun onServicesUpdated(list: List<CandidateInfo>) {
- uiExecutor.execute {
- listOfServices = list.sortedBy {
- it.loadLabel().toString()
+ backgroundExecutor.execute {
+ val collator = Collator.getInstance(resources.getConfiguration().locale)
+ val localeComparator = compareBy<CandidateInfo, CharSequence>(collator) {
+ it.loadLabel()
}
- notifyDataSetChanged()
+ listOfServices = list.sortedWith(localeComparator)
+ uiExecutor.execute(::notifyDataSetChanged)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
index 65dcc2b193d5..d3cabe67790e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
@@ -25,101 +25,150 @@ import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.ImageView
import android.widget.TextView
+import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.android.systemui.R
-import com.android.systemui.controls.ControlStatus
-import com.android.systemui.controls.controller.ControlInfo
import com.android.systemui.controls.ui.RenderInfo
+private typealias ModelFavoriteChanger = (String, Boolean) -> Unit
+
/**
* Adapter for binding [Control] information to views.
*
+ * The model for this adapter is provided by a [FavoriteModel] that is set using
+ * [changeFavoritesModel]. This allows for updating the model if there's a reload.
+ *
* @param layoutInflater an inflater for the views in the containing [RecyclerView]
- * @param favoriteCallback a callback to be called when the favorite status of a [Control] is
- * changed. The callback will take a [ControlInfo.Builder] that's
- * pre-populated with the [Control] information and the new favorite
- * status.
+ * @param onlyFavorites set to true to only display favorites instead of all controls
*/
class ControlAdapter(
private val layoutInflater: LayoutInflater,
- private val favoriteCallback: (ControlInfo.Builder, Boolean) -> Unit
-) : RecyclerView.Adapter<ControlAdapter.Holder>() {
+ private val onlyFavorites: Boolean = false
+) : RecyclerView.Adapter<Holder>() {
+
+ companion object {
+ private const val TYPE_ZONE = 0
+ private const val TYPE_CONTROL = 1
+ }
+
+ val spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
+ override fun getSpanSize(position: Int): Int {
+ return if (getItemViewType(position) == TYPE_ZONE) 2 else 1
+ }
+ }
- var listOfControls = emptyList<ControlStatus>()
+ var modelList: List<ElementWrapper> = emptyList()
+ private var favoritesModel: FavoriteModel? = null
- override fun onCreateViewHolder(parent: ViewGroup, i: Int): Holder {
- return Holder(layoutInflater.inflate(R.layout.controls_base_item, parent, false).apply {
- layoutParams.apply {
- width = ViewGroup.LayoutParams.MATCH_PARENT
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
+ return when (viewType) {
+ TYPE_CONTROL -> {
+ ControlHolder(
+ layoutInflater.inflate(R.layout.controls_base_item, parent, false).apply {
+ layoutParams.apply {
+ width = ViewGroup.LayoutParams.MATCH_PARENT
+ }
+ elevation = 15f
+ },
+ { id, favorite ->
+ favoritesModel?.changeFavoriteStatus(id, favorite)
+ })
+ }
+ TYPE_ZONE -> {
+ ZoneHolder(layoutInflater.inflate(R.layout.controls_zone_header, parent, false))
}
- elevation = 15f
- })
+ else -> throw IllegalStateException("Wrong viewType: $viewType")
+ }
}
- override fun getItemCount() = listOfControls.size
+ fun changeFavoritesModel(favoritesModel: FavoriteModel) {
+ this.favoritesModel = favoritesModel
+ if (onlyFavorites) {
+ modelList = favoritesModel.favorites
+ } else {
+ modelList = favoritesModel.all
+ }
+ notifyDataSetChanged()
+ }
+
+ override fun getItemCount() = modelList.size
override fun onBindViewHolder(holder: Holder, index: Int) {
- holder.bindData(listOfControls[index], favoriteCallback)
+ holder.bindData(modelList[index])
}
+ override fun getItemViewType(position: Int): Int {
+ return when (modelList[position]) {
+ is ZoneNameWrapper -> TYPE_ZONE
+ is ControlWrapper -> TYPE_CONTROL
+ }
+ }
+}
+
+/**
+ * Holder for binding views in the [RecyclerView]-
+ * @param view the [View] for this [Holder]
+ */
+sealed class Holder(view: View) : RecyclerView.ViewHolder(view) {
+
/**
- * Holder for binding views in the [RecyclerView]-
+ * Bind the data from the model into the view
*/
- class Holder(view: View) : RecyclerView.ViewHolder(view) {
- private val icon: ImageView = itemView.requireViewById(R.id.icon)
- private val title: TextView = itemView.requireViewById(R.id.title)
- private val subtitle: TextView = itemView.requireViewById(R.id.subtitle)
- private val removed: TextView = itemView.requireViewById(R.id.status)
- private val favorite: CheckBox = itemView.requireViewById<CheckBox>(R.id.favorite).apply {
- visibility = View.VISIBLE
- }
+ abstract fun bindData(wrapper: ElementWrapper)
+}
- /**
- * Bind data to the view
- * @param data information about the [Control]
- * @param callback a callback to be called when the favorite status of the [Control] is
- * changed. The callback will take a [ControlInfo.Builder] that's
- * pre-populated with the [Control] information and the new favorite status.
- */
- fun bindData(data: ControlStatus, callback: (ControlInfo.Builder, Boolean) -> Unit) {
- val renderInfo = getRenderInfo(data.control.deviceType, data.favorite)
- title.text = data.control.title
- subtitle.text = data.control.subtitle
- favorite.isChecked = data.favorite
- removed.text = if (data.removed) "Removed" else ""
- favorite.setOnClickListener {
- val infoBuilder = ControlInfo.Builder().apply {
- controlId = data.control.controlId
- controlTitle = data.control.title
- deviceType = data.control.deviceType
- }
- callback(infoBuilder, favorite.isChecked)
- }
- itemView.setOnClickListener {
- favorite.performClick()
- }
- applyRenderInfo(renderInfo)
- }
+/**
+ * Holder for using with [ZoneNameWrapper] to display names of zones.
+ */
+private class ZoneHolder(view: View) : Holder(view) {
+ private val zone: TextView = itemView as TextView
- private fun getRenderInfo(
- @DeviceTypes.DeviceType deviceType: Int,
- favorite: Boolean
- ): RenderInfo {
- return RenderInfo.lookup(deviceType, favorite)
- }
+ override fun bindData(wrapper: ElementWrapper) {
+ wrapper as ZoneNameWrapper
+ zone.text = wrapper.zoneName
+ }
+}
- private fun applyRenderInfo(ri: RenderInfo) {
- val context = itemView.context
- val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme())
+/**
+ * Holder for using with [ControlWrapper] to display names of zones.
+ * @param favoriteCallback this callback will be called whenever the favorite state of the
+ * [Control] this view represents changes.
+ */
+private class ControlHolder(view: View, val favoriteCallback: ModelFavoriteChanger) : Holder(view) {
+ private val icon: ImageView = itemView.requireViewById(R.id.icon)
+ private val title: TextView = itemView.requireViewById(R.id.title)
+ private val subtitle: TextView = itemView.requireViewById(R.id.subtitle)
+ private val removed: TextView = itemView.requireViewById(R.id.status)
+ private val favorite: CheckBox = itemView.requireViewById<CheckBox>(R.id.favorite).apply {
+ visibility = View.VISIBLE
+ }
- icon.setImageIcon(Icon.createWithResource(context, ri.iconResourceId))
- icon.setImageTintList(fg)
+ override fun bindData(wrapper: ElementWrapper) {
+ wrapper as ControlWrapper
+ val data = wrapper.controlStatus
+ val renderInfo = getRenderInfo(data.control.deviceType)
+ title.text = data.control.title
+ subtitle.text = data.control.subtitle
+ favorite.isChecked = data.favorite
+ removed.text = if (data.removed) "Removed" else ""
+ favorite.setOnClickListener {
+ favoriteCallback(data.control.controlId, favorite.isChecked)
}
+ applyRenderInfo(renderInfo)
}
- fun setItems(list: List<ControlStatus>) {
- listOfControls = list
- notifyDataSetChanged()
+ private fun getRenderInfo(
+ @DeviceTypes.DeviceType deviceType: Int
+ ): RenderInfo {
+ return RenderInfo.lookup(deviceType, true)
+ }
+
+ private fun applyRenderInfo(ri: RenderInfo) {
+ val context = itemView.context
+ val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme())
+
+ icon.setImageIcon(Icon.createWithResource(context, ri.iconResourceId))
+ icon.setImageTintList(fg)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index be5258344492..1e5237148188 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -18,10 +18,14 @@ package com.android.systemui.controls.management
import android.app.Activity
import android.content.ComponentName
+import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
+import android.view.ViewStub
+import android.widget.Button
import android.widget.TextView
import androidx.recyclerview.widget.GridLayoutManager
+import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -41,13 +45,36 @@ class ControlsFavoritingActivity @Inject constructor(
companion object {
private const val TAG = "ControlsFavoritingActivity"
const val EXTRA_APP = "extra_app_label"
- const val EXTRA_COMPONENT = "extra_component"
}
- private lateinit var recyclerView: RecyclerView
- private lateinit var adapter: ControlAdapter
+ private lateinit var recyclerViewAll: RecyclerView
+ private lateinit var adapterAll: ControlAdapter
+ private lateinit var recyclerViewFavorites: RecyclerView
+ private lateinit var adapterFavorites: ControlAdapter
private var component: ComponentName? = null
+ private var currentModel: FavoriteModel? = null
+ private var itemTouchHelperCallback = object : ItemTouchHelper.SimpleCallback(
+ /* dragDirs */ ItemTouchHelper.UP
+ or ItemTouchHelper.DOWN
+ or ItemTouchHelper.LEFT
+ or ItemTouchHelper.RIGHT,
+ /* swipeDirs */0
+ ) {
+ override fun onMove(
+ recyclerView: RecyclerView,
+ viewHolder: RecyclerView.ViewHolder,
+ target: RecyclerView.ViewHolder
+ ): Boolean {
+ return currentModel?.onMoveItem(
+ viewHolder.adapterPosition, target.adapterPosition) != null
+ }
+
+ override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {}
+
+ override fun isItemViewSwipeEnabled() = false
+ }
+
private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
private val startingUser = controller.currentUserId
@@ -62,41 +89,77 @@ class ControlsFavoritingActivity @Inject constructor(
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.controls_management)
+ requireViewById<ViewStub>(R.id.stub).apply {
+ layoutResource = R.layout.controls_management_favorites
+ inflate()
+ }
val app = intent.getCharSequenceExtra(EXTRA_APP)
- component = intent.getParcelableExtra<ComponentName>(EXTRA_COMPONENT)
-
- // If we have no component name, there's not much we can do.
- val callback = component?.let {
- { infoBuilder: ControlInfo.Builder, status: Boolean ->
- infoBuilder.componentName = it
- controller.changeFavoriteStatus(infoBuilder.build(), status)
- }
- } ?: { _, _ -> Unit }
+ component = intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME)
- recyclerView = requireViewById(R.id.list)
- adapter = ControlAdapter(LayoutInflater.from(applicationContext), callback)
- recyclerView.adapter = adapter
- recyclerView.layoutManager = GridLayoutManager(applicationContext, 2)
- val margin = resources.getDimensionPixelSize(R.dimen.controls_card_margin)
- recyclerView.addItemDecoration(MarginItemDecorator(margin, margin))
+ setUpRecyclerViews()
requireViewById<TextView>(R.id.title).text = app?.let { it }
?: resources.getText(R.string.controls_favorite_default_title)
requireViewById<TextView>(R.id.subtitle).text =
resources.getText(R.string.controls_favorite_subtitle)
- currentUserTracker.startTracking()
- }
+ requireViewById<Button>(R.id.done).setOnClickListener {
+ if (component == null) return@setOnClickListener
+ val favoritesForStorage = currentModel?.favorites?.map {
+ with(it.controlStatus.control) {
+ ControlInfo(component!!, controlId, title, deviceType)
+ }
+ }
+ if (favoritesForStorage != null) {
+ controller.replaceFavoritesForComponent(component!!, favoritesForStorage)
+ finishAffinity()
+ }
+ }
- override fun onResume() {
- super.onResume()
component?.let {
- controller.loadForComponent(it) {
+ controller.loadForComponent(it) { allControls, favoriteKeys ->
executor.execute {
- adapter.setItems(it)
+ val favoriteModel = FavoriteModel(
+ allControls,
+ favoriteKeys,
+ allAdapter = adapterAll,
+ favoritesAdapter = adapterFavorites)
+ adapterAll.changeFavoritesModel(favoriteModel)
+ adapterFavorites.changeFavoritesModel(favoriteModel)
+ currentModel = favoriteModel
}
}
}
+
+ currentUserTracker.startTracking()
+ }
+
+ private fun setUpRecyclerViews() {
+ val margin = resources.getDimensionPixelSize(R.dimen.controls_card_margin)
+ val itemDecorator = MarginItemDecorator(margin, margin)
+ val layoutInflater = LayoutInflater.from(applicationContext)
+
+ adapterAll = ControlAdapter(layoutInflater)
+ recyclerViewAll = requireViewById<RecyclerView>(R.id.listAll).apply {
+ adapter = adapterAll
+ layoutManager = GridLayoutManager(applicationContext, 2).apply {
+ spanSizeLookup = adapterAll.spanSizeLookup
+ }
+ addItemDecoration(itemDecorator)
+ }
+
+ adapterFavorites = ControlAdapter(layoutInflater, true)
+ recyclerViewFavorites = requireViewById<RecyclerView>(R.id.listFavorites).apply {
+ layoutManager = GridLayoutManager(applicationContext, 2)
+ adapter = adapterFavorites
+ addItemDecoration(itemDecorator)
+ }
+ ItemTouchHelper(itemTouchHelperCallback).attachToRecyclerView(recyclerViewFavorites)
+ }
+
+ override fun onDestroy() {
+ currentUserTracker.stopTracking()
+ super.onDestroy()
}
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
index 645e929d6a10..ad4bdefdff3e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -20,6 +20,7 @@ import android.content.ComponentName
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
+import android.view.ViewStub
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@@ -63,11 +64,21 @@ class ControlsProviderSelectorActivity @Inject constructor(
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.controls_management)
+ requireViewById<ViewStub>(R.id.stub).apply {
+ layoutResource = R.layout.controls_management_apps
+ inflate()
+ }
recyclerView = requireViewById(R.id.list)
- recyclerView.adapter = AppAdapter(executor, lifecycle, listingController,
- LayoutInflater.from(this), ::launchFavoritingActivity,
- FavoritesRenderer(resources, controlsController::countFavoritesForComponent))
+ recyclerView.adapter = AppAdapter(
+ backExecutor,
+ executor,
+ lifecycle,
+ listingController,
+ LayoutInflater.from(this),
+ ::launchFavoritingActivity,
+ FavoritesRenderer(resources, controlsController::countFavoritesForComponent),
+ resources)
recyclerView.layoutManager = LinearLayoutManager(applicationContext)
requireViewById<TextView>(R.id.title).text =
@@ -89,11 +100,16 @@ class ControlsProviderSelectorActivity @Inject constructor(
.apply {
putExtra(ControlsFavoritingActivity.EXTRA_APP,
listingController.getAppLabel(it))
- putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, it)
- flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
+ putExtra(Intent.EXTRA_COMPONENT_NAME, it)
+ flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
}
startActivity(intent)
}
}
}
+
+ override fun onDestroy() {
+ currentUserTracker.stopTracking()
+ super.onDestroy()
+ }
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/FavoriteModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/FavoriteModel.kt
new file mode 100644
index 000000000000..6bade0aeb998
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/FavoriteModel.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.management
+
+import android.text.TextUtils
+import android.util.Log
+import com.android.systemui.controls.ControlStatus
+import java.util.Collections
+import java.util.Comparator
+
+/**
+ * Model for keeping track of current favorites and their order.
+ *
+ * This model is to be used with two [ControlAdapter] one that shows only favorites in the current
+ * order and another that shows all controls, separated by zone. When the favorite state of any
+ * control is modified or when the favorites are reordered, the adapters are notified of the change.
+ *
+ * @param listControls list of all the [ControlStatus] to display. This includes controls currently
+ * marked as favorites as well as those that have been removed (not returned
+ * from load)
+ * @param listFavoritesIds list of the [Control.controlId] for all the favorites, including those
+ * that have been removed.
+ * @param favoritesAdapter [ControlAdapter] used by the [RecyclerView] that shows only favorites
+ * @param allAdapter [ControlAdapter] used by the [RecyclerView] that shows all controls
+ */
+class FavoriteModel(
+ private val listControls: List<ControlStatus>,
+ listFavoritesIds: List<String>,
+ private val favoritesAdapter: ControlAdapter,
+ private val allAdapter: ControlAdapter
+) {
+
+ companion object {
+ private const val TAG = "FavoriteModel"
+ }
+
+ /**
+ * List of favorite controls ([ControlWrapper]) in order.
+ *
+ * Initially, this list will give a list of wrappers in the order specified by the constructor
+ * variable `listFavoriteIds`.
+ *
+ * As the favorites are added, removed or moved, this list will keep track of those changes.
+ */
+ val favorites: List<ControlWrapper> = listFavoritesIds.map { id ->
+ ControlWrapper(listControls.first { it.control.controlId == id })
+ }.toMutableList()
+
+ /**
+ * List of all controls by zones.
+ *
+ * Lists all the controls with the zone names interleaved as a flat list. After each zone name,
+ * the controls in that zone are listed. Zones are listed in alphabetical order
+ */
+ val all: List<ElementWrapper> = listControls.groupBy { it.control.zone }
+ .mapKeys { it.key ?: "" } // map null to empty
+ .toSortedMap(CharSequenceComparator())
+ .flatMap {
+ val controls = it.value.map { ControlWrapper(it) }
+ if (!TextUtils.isEmpty(it.key)) {
+ listOf(ZoneNameWrapper(it.key)) + controls
+ } else {
+ controls
+ }
+ }
+
+ /**
+ * Change the favorite status of a [Control].
+ *
+ * This can be invoked from any of the [ControlAdapter]. It will change the status of that
+ * control and either add it to the list of favorites (at the end) or remove it from it.
+ *
+ * Removing the favorite status from a Removed control will make it disappear completely if
+ * changes are saved.
+ *
+ * @param controlId the id of the [Control] to change the status
+ * @param favorite `true` if and only if it's set to be a favorite.
+ */
+ fun changeFavoriteStatus(controlId: String, favorite: Boolean) {
+ favorites as MutableList
+ val index = all.indexOfFirst {
+ it is ControlWrapper && it.controlStatus.control.controlId == controlId
+ }
+ val control = (all[index] as ControlWrapper).controlStatus
+ if (control.favorite == favorite) {
+ Log.d(TAG, "Changing favorite to same state for ${control.control.controlId} ")
+ return
+ } else {
+ control.favorite = favorite
+ }
+ allAdapter.notifyItemChanged(index)
+ if (favorite) {
+ favorites.add(all[index] as ControlWrapper)
+ favoritesAdapter.notifyItemInserted(favorites.size - 1)
+ } else {
+ val i = favorites.indexOfFirst { it.controlStatus.control.controlId == controlId }
+ favorites.removeAt(i)
+ favoritesAdapter.notifyItemRemoved(i)
+ }
+ }
+
+ /**
+ * Move items in the model and notify the [favoritesAdapter].
+ */
+ fun onMoveItem(from: Int, to: Int) {
+ if (from < to) {
+ for (i in from until to) {
+ Collections.swap(favorites, i, i + 1)
+ }
+ } else {
+ for (i in from downTo to + 1) {
+ Collections.swap(favorites, i, i - 1)
+ }
+ }
+ favoritesAdapter.notifyItemMoved(from, to)
+ }
+}
+
+/**
+ * Compares [CharSequence] as [String].
+ *
+ * It will have empty strings as the first element
+ */
+class CharSequenceComparator : Comparator<CharSequence> {
+ override fun compare(p0: CharSequence?, p1: CharSequence?): Int {
+ if (p0 == null && p1 == null) return 0
+ else if (p0 == null && p1 != null) return -1
+ else if (p0 != null && p1 == null) return 1
+ return p0.toString().compareTo(p1.toString())
+ }
+}
+
+/**
+ * Wrapper classes for the different types of elements shown in the [RecyclerView]s in
+ * [ControlsFavoritingActivity].
+ */
+sealed class ElementWrapper
+data class ZoneNameWrapper(val zoneName: CharSequence) : ElementWrapper()
+data class ControlWrapper(val controlStatus: ControlStatus) : ElementWrapper() \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index 4e887262659e..ca2226f29528 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -167,6 +167,10 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi
// again, it will only show after the brightness sensor has stabilized,
// avoiding a potential flicker.
scrimOpacity = 255;
+ } else if (!mScreenOff && mLightSensor == null) {
+ // No light sensor but previous state turned the screen black. Make the scrim
+ // transparent and below views visible.
+ scrimOpacity = 0;
} else if (brightnessReady) {
// Only unblank scrim once brightness is ready.
scrimOpacity = computeScrimOpacity(sensorValue);
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index b3fc027d1ac7..a3cd5fdd771b 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -1045,7 +1045,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
Action action = getItem(position);
View view = action.create(mContext, convertView, parent, LayoutInflater.from(mContext));
view.setOnClickListener(v -> onClickItem(position));
- view.setOnLongClickListener(v -> onLongClickItem(position));
+ if (action instanceof LongPressAction) {
+ view.setOnLongClickListener(v -> onLongClickItem(position));
+ }
return view;
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
index 6f03f18ef64b..41b31306a931 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
@@ -345,6 +345,14 @@ public class PipBoundsHandler {
}
/**
+ * Sets the current bound with the currently store aspect ratio.
+ * @param stackBounds
+ */
+ public void transformBoundsToAspectRatio(Rect stackBounds) {
+ transformBoundsToAspectRatio(stackBounds, mAspectRatio, true);
+ }
+
+ /**
* Set the current bounds (or the default bounds if there are no current bounds) with the
* specified aspect ratio.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
index f9b18cf17abe..c7bfc06829b3 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
@@ -280,7 +280,9 @@ public class PipMenuActivityController {
if (mToActivityMessenger != null) {
Bundle data = new Bundle();
data.putInt(EXTRA_MENU_STATE, menuState);
- data.putParcelable(EXTRA_STACK_BOUNDS, stackBounds);
+ if (stackBounds != null) {
+ data.putParcelable(EXTRA_STACK_BOUNDS, stackBounds);
+ }
data.putParcelable(EXTRA_MOVEMENT_BOUNDS, movementBounds);
data.putBoolean(EXTRA_ALLOW_TIMEOUT, allowMenuTimeout);
data.putBoolean(EXTRA_WILL_RESIZE_MENU, willResizeMenu);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
new file mode 100644
index 000000000000..9fb623471bf7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.pip.phone;
+
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_USER_RESIZE;
+import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM;
+import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT;
+import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE;
+import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT;
+import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.hardware.input.InputManager;
+import android.os.Looper;
+import android.provider.DeviceConfig;
+import android.util.DisplayMetrics;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.InputMonitor;
+import android.view.MotionEvent;
+
+import com.android.internal.policy.TaskResizingAlgorithm;
+import com.android.systemui.R;
+import com.android.systemui.pip.PipBoundsHandler;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Helper on top of PipTouchHandler that handles inputs OUTSIDE of the PIP window, which is used to
+ * trigger dynamic resize.
+ */
+public class PipResizeGestureHandler {
+
+ private static final String TAG = "PipResizeGestureHandler";
+
+ private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
+ private final PipBoundsHandler mPipBoundsHandler;
+ private final PipTouchHandler mPipTouchHandler;
+ private final PipMotionHelper mMotionHelper;
+ private final int mDisplayId;
+ private final Executor mMainExecutor;
+ private final Region mTmpRegion = new Region();
+
+ private final PointF mDownPoint = new PointF();
+ private final Point mMaxSize = new Point();
+ private final Point mMinSize = new Point();
+ private final Rect mTmpBounds = new Rect();
+ private final int mDelta;
+
+ private boolean mAllowGesture = false;
+ private boolean mIsAttached;
+ private boolean mIsEnabled;
+ private boolean mEnablePipResize;
+
+ private InputMonitor mInputMonitor;
+ private InputEventReceiver mInputEventReceiver;
+
+ private int mCtrlType;
+
+ public PipResizeGestureHandler(Context context, PipBoundsHandler pipBoundsHandler,
+ PipTouchHandler pipTouchHandler, PipMotionHelper motionHelper) {
+ final Resources res = context.getResources();
+ context.getDisplay().getMetrics(mDisplayMetrics);
+ mDisplayId = context.getDisplayId();
+ mMainExecutor = context.getMainExecutor();
+ mPipBoundsHandler = pipBoundsHandler;
+ mPipTouchHandler = pipTouchHandler;
+ mMotionHelper = motionHelper;
+
+ context.getDisplay().getRealSize(mMaxSize);
+ mDelta = res.getDimensionPixelSize(R.dimen.pip_resize_edge_size);
+
+ mEnablePipResize = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ PIP_USER_RESIZE,
+ /* defaultValue = */ false);
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mMainExecutor,
+ new DeviceConfig.OnPropertiesChangedListener() {
+ @Override
+ public void onPropertiesChanged(DeviceConfig.Properties properties) {
+ if (properties.getKeyset().contains(PIP_USER_RESIZE)) {
+ mEnablePipResize = properties.getBoolean(
+ PIP_USER_RESIZE, /* defaultValue = */ false);
+ }
+ }
+ });
+ }
+
+ private void disposeInputChannel() {
+ if (mInputEventReceiver != null) {
+ mInputEventReceiver.dispose();
+ mInputEventReceiver = null;
+ }
+ if (mInputMonitor != null) {
+ mInputMonitor.dispose();
+ mInputMonitor = null;
+ }
+ }
+
+ void onActivityPinned() {
+ mIsAttached = true;
+ updateIsEnabled();
+ }
+
+ void onActivityUnpinned() {
+ mIsAttached = false;
+ updateIsEnabled();
+ }
+
+ private void updateIsEnabled() {
+ boolean isEnabled = mIsAttached && mEnablePipResize;
+ if (isEnabled == mIsEnabled) {
+ return;
+ }
+ mIsEnabled = isEnabled;
+ disposeInputChannel();
+
+ if (mIsEnabled) {
+ // Register input event receiver
+ mInputMonitor = InputManager.getInstance().monitorGestureInput(
+ "pip-resize", mDisplayId);
+ mInputEventReceiver = new SysUiInputEventReceiver(
+ mInputMonitor.getInputChannel(), Looper.getMainLooper());
+ }
+ }
+
+ private void onInputEvent(InputEvent ev) {
+ if (ev instanceof MotionEvent) {
+ onMotionEvent((MotionEvent) ev);
+ }
+ }
+
+ private boolean isWithinTouchRegion(int x, int y) {
+ final Rect currentPipBounds = mMotionHelper.getBounds();
+ if (currentPipBounds == null) {
+ return false;
+ }
+
+ mTmpBounds.set(currentPipBounds);
+ mTmpBounds.inset(-mDelta, -mDelta);
+
+ mTmpRegion.set(mTmpBounds);
+ mTmpRegion.op(currentPipBounds, Region.Op.DIFFERENCE);
+
+ if (mTmpRegion.contains(x, y)) {
+ if (x < currentPipBounds.left) {
+ mCtrlType |= CTRL_LEFT;
+ }
+ if (x > currentPipBounds.right) {
+ mCtrlType |= CTRL_RIGHT;
+ }
+ if (y < currentPipBounds.top) {
+ mCtrlType |= CTRL_TOP;
+ }
+ if (y > currentPipBounds.bottom) {
+ mCtrlType |= CTRL_BOTTOM;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private void onMotionEvent(MotionEvent ev) {
+ int action = ev.getActionMasked();
+ if (action == MotionEvent.ACTION_DOWN) {
+ mAllowGesture = isWithinTouchRegion((int) ev.getX(), (int) ev.getY());
+ if (mAllowGesture) {
+ mDownPoint.set(ev.getX(), ev.getY());
+ }
+
+ } else if (mAllowGesture) {
+ final Rect currentPipBounds = mMotionHelper.getBounds();
+ Rect newSize = TaskResizingAlgorithm.resizeDrag(ev.getX(), ev.getY(), mDownPoint.x,
+ mDownPoint.y, currentPipBounds, mCtrlType, mMinSize.x, mMinSize.y, mMaxSize,
+ true, true);
+ mPipBoundsHandler.transformBoundsToAspectRatio(newSize);
+ switch (action) {
+ case MotionEvent.ACTION_POINTER_DOWN:
+ // We do not support multi touch for resizing via drag
+ mAllowGesture = false;
+ break;
+ case MotionEvent.ACTION_MOVE:
+ // Capture inputs
+ mInputMonitor.pilferPointers();
+ //TODO: Actually do resize here.
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ //TODO: Finish resize operation here.
+ mMotionHelper.synchronizePinnedStackBounds();
+ mCtrlType = CTRL_NONE;
+ mAllowGesture = false;
+ break;
+ }
+ }
+ }
+
+ void updateMaxSize(int maxX, int maxY) {
+ mMaxSize.set(maxX, maxY);
+ }
+
+ void updateMiniSize(int minX, int minY) {
+ mMinSize.set(minX, minY);
+ }
+
+ class SysUiInputEventReceiver extends InputEventReceiver {
+ SysUiInputEventReceiver(InputChannel channel, Looper looper) {
+ super(channel, looper);
+ }
+
+ public void onInputEvent(InputEvent event) {
+ PipResizeGestureHandler.this.onInputEvent(event);
+ finishInputEvent(event, true);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 65cc666d5164..924edb6fe312 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -73,6 +73,7 @@ public class PipTouchHandler {
private final ViewConfiguration mViewConfig;
private final PipMenuListener mMenuListener = new PipMenuListener();
private final PipBoundsHandler mPipBoundsHandler;
+ private final PipResizeGestureHandler mPipResizeGestureHandler;
private IPinnedStackController mPinnedStackController;
private final PipMenuActivityController mMenuController;
@@ -188,6 +189,8 @@ public class PipTouchHandler {
mGesture = new DefaultPipTouchGesture();
mMotionHelper = new PipMotionHelper(mContext, mActivityManager, mActivityTaskManager,
mMenuController, mSnapAlgorithm, mFlingAnimationUtils);
+ mPipResizeGestureHandler =
+ new PipResizeGestureHandler(context, pipBoundsHandler, this, mMotionHelper);
mTouchState = new PipTouchState(mViewConfig, mHandler,
() -> mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(),
mMovementBounds, true /* allowMenuTimeout */, willResizeMenu()));
@@ -227,6 +230,7 @@ public class PipTouchHandler {
public void onActivityPinned() {
cleanUp();
mShowPipMenuOnAnimationEnd = true;
+ mPipResizeGestureHandler.onActivityPinned();
}
public void onActivityUnpinned(ComponentName topPipActivity) {
@@ -234,11 +238,14 @@ public class PipTouchHandler {
// Clean up state after the last PiP activity is removed
cleanUp();
}
+ mPipResizeGestureHandler.onActivityUnpinned();
}
public void onPinnedStackAnimationEnded() {
// Always synchronize the motion helper bounds once PiP animations finish
mMotionHelper.synchronizePinnedStackBounds();
+ mPipResizeGestureHandler.updateMiniSize(mMotionHelper.getBounds().width(),
+ mMotionHelper.getBounds().height());
if (mShowPipMenuOnAnimationEnd) {
mMenuController.showMenu(MENU_STATE_CLOSE, mMotionHelper.getBounds(),
@@ -279,6 +286,7 @@ public class PipTouchHandler {
Size expandedSize = mSnapAlgorithm.getSizeForAspectRatio(aspectRatio,
mExpandedShortestEdgeSize, displaySize.x, displaySize.y);
mExpandedBounds.set(0, 0, expandedSize.getWidth(), expandedSize.getHeight());
+ mPipResizeGestureHandler.updateMaxSize(expandedSize.getWidth(), expandedSize.getHeight());
Rect expandedMovementBounds = new Rect();
mSnapAlgorithm.getMovementBounds(mExpandedBounds, insetBounds, expandedMovementBounds,
bottomOffset);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index ae6162219afa..c118630fe91a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -191,6 +191,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener
mTile.setLabel(tile.getLabel());
mTile.setSubtitle(tile.getSubtitle());
mTile.setContentDescription(tile.getContentDescription());
+ mTile.setStateDescription(tile.getStateDescription());
mTile.setState(tile.getState());
}
@@ -345,6 +346,12 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener
state.contentDescription = state.label;
}
+ if (mTile.getStateDescription() != null) {
+ state.stateDescription = mTile.getStateDescription();
+ } else {
+ state.stateDescription = null;
+ }
+
if (state instanceof BooleanState) {
state.expandedAccessibilityClassName = Switch.class.getName();
((BooleanState) state).value = (state.state == Tile.STATE_ACTIVE);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
index 2fe64d26f3ac..8feee10c7e83 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
@@ -63,7 +63,6 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView {
private String mAccessibilityClass;
private boolean mTileState;
private boolean mCollapsedView;
- private boolean mClicked;
private boolean mShowRippleEffect = true;
private final ImageView mBg;
@@ -230,13 +229,35 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView {
setLongClickable(state.handlesLongClick);
mIcon.setIcon(state, allowAnimations);
setContentDescription(state.contentDescription);
+ final StringBuilder stateDescription = new StringBuilder();
+ switch (state.state) {
+ case Tile.STATE_UNAVAILABLE:
+ stateDescription.append(mContext.getString(R.string.tile_unavailable));
+ break;
+ case Tile.STATE_INACTIVE:
+ if (state instanceof QSTile.BooleanState) {
+ stateDescription.append(mContext.getString(R.string.switch_bar_off));
+ }
+ break;
+ case Tile.STATE_ACTIVE:
+ if (state instanceof QSTile.BooleanState) {
+ stateDescription.append(mContext.getString(R.string.switch_bar_on));
+ }
+ break;
+ default:
+ break;
+ }
+ if (!TextUtils.isEmpty(state.stateDescription)) {
+ stateDescription.append(", ");
+ stateDescription.append(state.stateDescription);
+ }
+ setStateDescription(stateDescription.toString());
mAccessibilityClass =
state.state == Tile.STATE_UNAVAILABLE ? null : state.expandedAccessibilityClassName;
if (state instanceof QSTile.BooleanState) {
boolean newState = ((BooleanState) state).value;
if (mTileState != newState) {
- mClicked = false;
mTileState = newState;
}
}
@@ -288,23 +309,10 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView {
}
@Override
- public boolean performClick() {
- mClicked = true;
- return super.performClick();
- }
-
- @Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
if (!TextUtils.isEmpty(mAccessibilityClass)) {
event.setClassName(mAccessibilityClass);
- if (Switch.class.getName().equals(mAccessibilityClass)) {
- boolean b = mClicked ? !mTileState : mTileState;
- String label = getResources()
- .getString(b ? R.string.switch_bar_on : R.string.switch_bar_off);
- event.setContentDescription(label);
- event.setChecked(b);
- }
}
}
@@ -316,11 +324,13 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView {
if (!TextUtils.isEmpty(mAccessibilityClass)) {
info.setClassName(mAccessibilityClass);
if (Switch.class.getName().equals(mAccessibilityClass)) {
- boolean b = mClicked ? !mTileState : mTileState;
- String label = getResources()
- .getString(b ? R.string.switch_bar_on : R.string.switch_bar_off);
+ String label = getResources().getString(
+ mTileState ? R.string.switch_bar_on : R.string.switch_bar_off);
+ // Set the text here for tests in
+ // android.platform.test.scenario.sysui.quicksettings. Can be removed when
+ // UiObject2 has a new getStateDescription() API and tests are updated.
info.setText(label);
- info.setChecked(b);
+ info.setChecked(mTileState);
info.setCheckable(true);
if (isLongClickable()) {
info.addAction(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 9282a2e3b312..361b6c1b1260 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -134,25 +134,27 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
state.secondaryLabel = TextUtils.emptyIfNull(
getSecondaryLabel(enabled, connecting, connected, state.isTransient));
+ state.contentDescription = state.label;
+ state.stateDescription = "";
if (enabled) {
if (connected) {
state.icon = new BluetoothConnectedTileIcon();
if (!TextUtils.isEmpty(mController.getConnectedDeviceName())) {
state.label = mController.getConnectedDeviceName();
}
- state.contentDescription =
+ state.stateDescription =
mContext.getString(R.string.accessibility_bluetooth_name, state.label)
+ ", " + state.secondaryLabel;
} else if (state.isTransient) {
state.icon = ResourceIcon.get(
com.android.internal.R.drawable.ic_bluetooth_transient_animation);
- state.contentDescription = state.secondaryLabel;
+ state.stateDescription = state.secondaryLabel;
} else {
state.icon =
ResourceIcon.get(com.android.internal.R.drawable.ic_qs_bluetooth);
state.contentDescription = mContext.getString(
- R.string.accessibility_quick_settings_bluetooth) + ","
- + mContext.getString(R.string.accessibility_not_connected);
+ R.string.accessibility_quick_settings_bluetooth);
+ state.stateDescription = mContext.getString(R.string.accessibility_not_connected);
}
state.state = Tile.STATE_ACTIVE;
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 32b051e35604..58de0575fa75 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -183,6 +183,7 @@ public class CastTile extends QSTileImpl<BooleanState> {
protected void handleUpdateState(BooleanState state, Object arg) {
state.label = mContext.getString(R.string.quick_settings_cast_title);
state.contentDescription = state.label;
+ state.stateDescription = "";
state.value = false;
final List<CastDevice> devices = mController.getCastDevices();
boolean connecting = false;
@@ -192,8 +193,9 @@ public class CastTile extends QSTileImpl<BooleanState> {
if (device.state == CastDevice.STATE_CONNECTED) {
state.value = true;
state.secondaryLabel = getDeviceName(device);
- state.contentDescription = state.contentDescription + ","
- + mContext.getString(R.string.accessibility_cast_name, state.label);
+ state.stateDescription = state.stateDescription + ","
+ + mContext.getString(
+ R.string.accessibility_cast_name, state.label);
connecting = false;
break;
} else if (device.state == CastDevice.STATE_CONNECTING) {
@@ -217,9 +219,8 @@ public class CastTile extends QSTileImpl<BooleanState> {
state.state = Tile.STATE_UNAVAILABLE;
String noWifi = mContext.getString(R.string.quick_settings_cast_no_wifi);
state.secondaryLabel = noWifi;
- state.contentDescription = state.contentDescription + ", " + mContext.getString(
- R.string.accessibility_quick_settings_not_available, noWifi);
}
+ state.stateDescription = state.stateDescription + ", " + state.secondaryLabel;
mDetailAdapter.updateItems(devices);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 22470c7f5af5..d5f86c951407 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -194,17 +194,13 @@ public class CellularTile extends QSTileImpl<SignalState> {
state.secondaryLabel = r.getString(R.string.cell_data_off);
}
-
- // TODO(b/77881974): Instead of switching out the description via a string check for
- // we need to have two strings provided by the MobileIconGroup.
- final CharSequence contentDescriptionSuffix;
+ state.contentDescription = state.label;
if (state.state == Tile.STATE_INACTIVE) {
- contentDescriptionSuffix = r.getString(R.string.cell_data_off_content_description);
+ // This information is appended later by converting the Tile.STATE_INACTIVE state.
+ state.stateDescription = "";
} else {
- contentDescriptionSuffix = state.secondaryLabel;
+ state.stateDescription = state.secondaryLabel;
}
-
- state.contentDescription = state.label + ", " + contentDescriptionSuffix;
}
private CharSequence appendMobileDataType(CharSequence current, CharSequence dataType) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 52d1a5b3b991..9215da4cda9a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -240,6 +240,8 @@ public class DndTile extends QSTileImpl<BooleanState> {
zen != Global.ZEN_MODE_OFF, mController.getConfig(), false));
state.icon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_dnd);
checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_ADJUST_VOLUME);
+ // Keeping the secondaryLabel in contentDescription instead of stateDescription is easier
+ // to understand.
switch (zen) {
case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
state.contentDescription =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
index dafdd89ee62c..792c36477962 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -102,14 +102,13 @@ public class FlashlightTile extends QSTileImpl<BooleanState> implements
}
state.label = mHost.getContext().getString(R.string.quick_settings_flashlight_label);
state.secondaryLabel = "";
+ state.stateDescription = "";
if (!mFlashlightController.isAvailable()) {
state.icon = mIcon;
state.slash.isSlashed = true;
state.secondaryLabel = mContext.getString(
R.string.quick_settings_flashlight_camera_in_use);
- state.contentDescription = mContext.getString(
- R.string.accessibility_quick_settings_flashlight_unavailable)
- + ", " + state.secondaryLabel;
+ state.stateDescription = state.secondaryLabel;
state.state = Tile.STATE_UNAVAILABLE;
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 42f80109e045..91b3ae480af9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -148,6 +148,7 @@ public class HotspotTile extends QSTileImpl<BooleanState> {
state.secondaryLabel = getSecondaryLabel(
isTileActive, isTransient, isDataSaverEnabled, numConnectedDevices);
+ state.stateDescription = state.secondaryLabel;
}
@Nullable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index fbdca3ba1c7b..e617867eb10e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -105,15 +105,8 @@ public class LocationTile extends QSTileImpl<BooleanState> {
}
state.icon = mIcon;
state.slash.isSlashed = !state.value;
- if (locationEnabled) {
- state.label = mContext.getString(R.string.quick_settings_location_label);
- state.contentDescription = mContext.getString(
- R.string.accessibility_quick_settings_location_on);
- } else {
- state.label = mContext.getString(R.string.quick_settings_location_label);
- state.contentDescription = mContext.getString(
- R.string.accessibility_quick_settings_location_off);
- }
+ state.label = mContext.getString(R.string.quick_settings_location_label);
+ state.contentDescription = state.label;
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
state.expandedAccessibilityClassName = Switch.class.getName();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index b7ce101cacab..6e8dcf36bacc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -195,6 +195,7 @@ public class WifiTile extends QSTileImpl<SignalState> {
state.activityIn = cb.enabled && cb.activityIn;
state.activityOut = cb.enabled && cb.activityOut;
final StringBuffer minimalContentDescription = new StringBuffer();
+ final StringBuffer minimalStateDescription = new StringBuffer();
final Resources r = mContext.getResources();
if (isTransient) {
state.icon = ResourceIcon.get(
@@ -219,13 +220,14 @@ public class WifiTile extends QSTileImpl<SignalState> {
mContext.getString(R.string.quick_settings_wifi_label)).append(",");
if (state.value) {
if (wifiConnected) {
- minimalContentDescription.append(cb.wifiSignalContentDescription).append(",");
+ minimalStateDescription.append(cb.wifiSignalContentDescription);
minimalContentDescription.append(removeDoubleQuotes(cb.ssid));
if (!TextUtils.isEmpty(state.secondaryLabel)) {
minimalContentDescription.append(",").append(state.secondaryLabel);
}
}
}
+ state.stateDescription = minimalStateDescription.toString();
state.contentDescription = minimalContentDescription.toString();
state.dualLabelContentDescription = r.getString(
R.string.accessibility_quick_settings_open_settings, getTileLabel());
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 7853dc388bcb..e54ee51fb9d4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -103,14 +103,11 @@ public class WorkModeTile extends QSTileImpl<BooleanState> implements
state.icon = mIcon;
if (state.value) {
state.slash.isSlashed = false;
- state.contentDescription = mContext.getString(
- R.string.accessibility_quick_settings_work_mode_on);
} else {
state.slash.isSlashed = true;
- state.contentDescription = mContext.getString(
- R.string.accessibility_quick_settings_work_mode_off);
}
state.label = mContext.getString(R.string.quick_settings_work_mode_label);
+ state.contentDescription = state.label;
state.expandedAccessibilityClassName = Switch.class.getName();
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 880b8f8776e8..4f38a15824a4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -69,6 +69,7 @@ import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.view.animation.Interpolator;
+import android.widget.FrameLayout;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -143,7 +144,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
private static final float BACKGROUND_ALPHA = 0.5f;
private static final float SCREENSHOT_DROP_IN_MIN_SCALE = 0.725f;
private static final float ROUNDED_CORNER_RADIUS = .05f;
- private static final long SCREENSHOT_CORNER_TIMEOUT_MILLIS = 8000;
+ private static final long SCREENSHOT_CORNER_TIMEOUT_MILLIS = 6000;
private static final int MESSAGE_CORNER_TIMEOUT = 2;
private final ScreenshotNotificationsController mNotificationsController;
@@ -162,6 +163,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
private final HorizontalScrollView mActionsContainer;
private final LinearLayout mActionsView;
private final ImageView mBackgroundProtection;
+ private final FrameLayout mDismissButton;
private Bitmap mScreenBitmap;
private AnimatorSet mScreenshotAnimation;
@@ -170,6 +172,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
private float mScreenshotOffsetYPx;
private float mScreenshotHeightPx;
private float mCornerScale;
+ private float mDismissButtonSize;
private AsyncTask<Void, Void, Void> mSaveInBgTask;
@@ -216,19 +219,14 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
mActionsView = mScreenshotLayout.findViewById(R.id.global_screenshot_actions);
mBackgroundProtection = mScreenshotLayout.findViewById(
R.id.global_screenshot_actions_background);
+ mDismissButton = mScreenshotLayout.findViewById(R.id.global_screenshot_dismiss_button);
+ mDismissButton.setOnClickListener(view -> clearScreenshot("dismiss_button"));
mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_flash);
mScreenshotSelectorView = mScreenshotLayout.findViewById(R.id.global_screenshot_selector);
mScreenshotLayout.setFocusable(true);
mScreenshotSelectorView.setFocusable(true);
mScreenshotSelectorView.setFocusableInTouchMode(true);
- mScreenshotLayout.setOnTouchListener((v, event) -> {
- if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
- clearScreenshot("tap_outside");
- }
- // Intercept and ignore all touch events
- return true;
- });
// Setup the window that we are going to use
mWindowLayoutParams = new WindowManager.LayoutParams(
@@ -254,6 +252,8 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
resources.getDimensionPixelSize(R.dimen.screenshot_action_container_offset_y);
mCornerScale = resources.getDimensionPixelSize(R.dimen.global_screenshot_x_scale)
/ (float) mDisplayMetrics.widthPixels;
+ mDismissButtonSize = resources.getDimensionPixelSize(
+ R.dimen.screenshot_dismiss_button_tappable_size);
// Setup the Camera shutter sound
mCameraSound = new MediaActionSound();
@@ -271,6 +271,9 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
Rect actionsRect = new Rect();
mActionsContainer.getBoundsOnScreen(actionsRect);
touchRegion.op(actionsRect, Region.Op.UNION);
+ Rect dismissRect = new Rect();
+ mDismissButton.getBoundsOnScreen(dismissRect);
+ touchRegion.op(dismissRect, Region.Op.UNION);
inoutInfo.touchableRegion.set(touchRegion);
}
@@ -408,6 +411,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
mActionsContainer.setVisibility(View.GONE);
mBackgroundView.setVisibility(View.GONE);
mBackgroundProtection.setAlpha(0f);
+ mDismissButton.setVisibility(View.GONE);
mScreenshotView.setVisibility(View.GONE);
mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
}
@@ -615,6 +619,17 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
mScreenshotView.setTranslationX(t * finalPos.x);
mScreenshotView.setTranslationY(t * finalPos.y);
});
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ Rect bounds = new Rect();
+ mScreenshotView.getBoundsOnScreen(bounds);
+ mDismissButton.setX(bounds.right - mDismissButtonSize / 2f);
+ mDismissButton.setY(bounds.top - mDismissButtonSize / 2f);
+ mDismissButton.setVisibility(View.VISIBLE);
+ }
+ });
return anim;
}
@@ -686,14 +701,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
mBackgroundProtection.setAlpha(t);
mActionsContainer.setY(mDisplayMetrics.heightPixels - actionsViewHeight * t);
});
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- mScreenshotView.requestFocus();
- mScreenshotView.setElevation(50);
- }
- });
return animator;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index bb0681ce8a44..a0af4ace05b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -23,14 +23,6 @@ import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
-import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_BUBBLE;
-import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_DEMOTE;
-import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_FAVORITE;
-import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_HOME;
-import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_MUTE;
-import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_SNOOZE;
-import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_UNBUBBLE;
-
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.IntDef;
@@ -69,11 +61,13 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.phone.ShadeController;
import java.lang.annotation.Retention;
import java.util.Arrays;
@@ -92,6 +86,7 @@ public class NotificationConversationInfo extends LinearLayout implements
ShortcutManager mShortcutManager;
private PackageManager mPm;
private VisualStabilityManager mVisualStabilityManager;
+ private ShadeController mShadeController;
private String mPackageName;
private String mAppName;
@@ -103,24 +98,30 @@ public class NotificationConversationInfo extends LinearLayout implements
private NotificationEntry mEntry;
private StatusBarNotification mSbn;
private boolean mIsDeviceProvisioned;
- private int mStartingChannelImportance;
private boolean mStartedAsBubble;
private boolean mIsBubbleable;
- // TODO: remove when launcher api works
- @VisibleForTesting
- boolean mShowHomeScreen = false;
- private @UpdateChannelRunnable.Action int mSelectedAction = -1;
+ private @Action int mSelectedAction = -1;
private OnSnoozeClickListener mOnSnoozeClickListener;
private OnSettingsClickListener mOnSettingsClickListener;
- private OnAppSettingsClickListener mAppSettingsClickListener;
private NotificationGuts mGutsContainer;
private BubbleController mBubbleController;
@VisibleForTesting
boolean mSkipPost = false;
+ @Retention(SOURCE)
+ @IntDef({ACTION_BUBBLE, ACTION_HOME, ACTION_FAVORITE, ACTION_SNOOZE, ACTION_MUTE})
+ private @interface Action {}
+ static final int ACTION_BUBBLE = 0;
+ static final int ACTION_HOME = 1;
+ static final int ACTION_FAVORITE = 2;
+ static final int ACTION_SNOOZE = 3;
+ static final int ACTION_MUTE = 4;
+ static final int ACTION_SETTINGS = 5;
+ static final int ACTION_UNBUBBLE = 6;
+
private OnClickListener mOnBubbleClick = v -> {
mSelectedAction = mStartedAsBubble ? ACTION_UNBUBBLE : ACTION_BUBBLE;
if (mStartedAsBubble) {
@@ -136,12 +137,14 @@ public class NotificationConversationInfo extends LinearLayout implements
private OnClickListener mOnHomeClick = v -> {
mSelectedAction = ACTION_HOME;
mShortcutManager.requestPinShortcut(mShortcutInfo, null);
+ mShadeController.animateCollapsePanels();
closeControls(v, true);
};
private OnClickListener mOnFavoriteClick = v -> {
mSelectedAction = ACTION_FAVORITE;
- closeControls(v, true);
+ updateChannel();
+
};
private OnClickListener mOnSnoozeClick = v -> {
@@ -151,13 +154,8 @@ public class NotificationConversationInfo extends LinearLayout implements
};
private OnClickListener mOnMuteClick = v -> {
- mSelectedAction = ACTION_MUTE;
- closeControls(v, true);
- };
-
- private OnClickListener mOnDemoteClick = v -> {
- mSelectedAction = ACTION_DEMOTE;
- closeControls(v, true);
+ mSelectedAction = ACTION_MUTE;
+ updateChannel();
};
public NotificationConversationInfo(Context context, AttributeSet attrs) {
@@ -197,15 +195,14 @@ public class NotificationConversationInfo extends LinearLayout implements
mEntry = entry;
mSbn = entry.getSbn();
mPm = pm;
- mAppSettingsClickListener = onAppSettingsClick;
mAppName = mPackageName;
mOnSettingsClickListener = onSettingsClick;
mNotificationChannel = notificationChannel;
- mStartingChannelImportance = mNotificationChannel.getImportance();
mAppUid = mSbn.getUid();
mDelegatePkg = mSbn.getOpPkg();
mIsDeviceProvisioned = isDeviceProvisioned;
mOnSnoozeClickListener = onSnoozeClickListener;
+ mShadeController = Dependency.get(ShadeController.class);
mShortcutManager = shortcutManager;
mLauncherApps = launcherApps;
@@ -251,9 +248,6 @@ public class NotificationConversationInfo extends LinearLayout implements
mNotificationChannel = mINotificationManager.getConversationNotificationChannel(
mContext.getOpPackageName(), UserHandle.getUserId(mAppUid), mPackageName,
mNotificationChannel.getId(), false, mConversationId);
-
- // TODO: ask LA to pin the shortcut once api exists for pinning one shortcut at a
- // time
} catch (RemoteException e) {
Slog.e(TAG, "Could not create conversation channel", e);
}
@@ -274,40 +268,24 @@ public class NotificationConversationInfo extends LinearLayout implements
Button home = findViewById(R.id.home);
home.setOnClickListener(mOnHomeClick);
- home.setVisibility(mShowHomeScreen && mShortcutInfo != null
+ home.setVisibility(mShortcutInfo != null
&& mShortcutManager.isRequestPinShortcutSupported()
? VISIBLE : GONE);
- Button favorite = findViewById(R.id.fave);
+ View favorite = findViewById(R.id.fave);
favorite.setOnClickListener(mOnFavoriteClick);
- if (mNotificationChannel.isImportantConversation()) {
- favorite.setText(R.string.notification_conversation_unfavorite);
- favorite.setCompoundDrawablesRelative(
- mContext.getDrawable(R.drawable.ic_star), null, null, null);
- } else {
- favorite.setText(R.string.notification_conversation_favorite);
- favorite.setCompoundDrawablesRelative(
- mContext.getDrawable(R.drawable.ic_star_border), null, null, null);
- }
Button snooze = findViewById(R.id.snooze);
snooze.setOnClickListener(mOnSnoozeClick);
- Button mute = findViewById(R.id.mute);
+ View mute = findViewById(R.id.mute);
mute.setOnClickListener(mOnMuteClick);
- if (mStartingChannelImportance >= IMPORTANCE_DEFAULT
- || mStartingChannelImportance == IMPORTANCE_UNSPECIFIED) {
- mute.setText(R.string.notification_conversation_mute);
- favorite.setCompoundDrawablesRelative(
- mContext.getDrawable(R.drawable.ic_notifications_silence), null, null, null);
- } else {
- mute.setText(R.string.notification_conversation_unmute);
- favorite.setCompoundDrawablesRelative(
- mContext.getDrawable(R.drawable.ic_notifications_alert), null, null, null);
- }
- ImageButton demote = findViewById(R.id.demote);
- demote.setOnClickListener(mOnDemoteClick);
+ final View settingsButton = findViewById(R.id.info);
+ settingsButton.setOnClickListener(getSettingsOnClickListener());
+ settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE);
+
+ updateToggleActions();
}
private void bindHeader() {
@@ -315,26 +293,6 @@ public class NotificationConversationInfo extends LinearLayout implements
// Delegate
bindDelegate();
-
- // Set up app settings link (i.e. Customize)
- View settingsLinkView = findViewById(R.id.app_settings);
- Intent settingsIntent = getAppSettingsIntent(mPm, mPackageName,
- mNotificationChannel,
- mSbn.getId(), mSbn.getTag());
- if (settingsIntent != null
- && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) {
- settingsLinkView.setVisibility(VISIBLE);
- settingsLinkView.setOnClickListener((View view) -> {
- mAppSettingsClickListener.onClick(view, settingsIntent);
- });
- } else {
- settingsLinkView.setVisibility(View.GONE);
- }
-
- // System Settings button.
- final View settingsButton = findViewById(R.id.info);
- settingsButton.setOnClickListener(getSettingsOnClickListener());
- settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE);
}
private OnClickListener getSettingsOnClickListener() {
@@ -424,15 +382,12 @@ public class NotificationConversationInfo extends LinearLayout implements
private void bindDelegate() {
TextView delegateView = findViewById(R.id.delegate_name);
- TextView dividerView = findViewById(R.id.pkg_divider);
if (!TextUtils.equals(mPackageName, mDelegatePkg)) {
// this notification was posted by a delegate!
delegateView.setVisibility(View.VISIBLE);
- dividerView.setVisibility(View.VISIBLE);
} else {
delegateView.setVisibility(View.GONE);
- dividerView.setVisibility(View.GONE);
}
}
@@ -492,26 +447,37 @@ public class NotificationConversationInfo extends LinearLayout implements
}
}
- private Intent getAppSettingsIntent(PackageManager pm, String packageName,
- NotificationChannel channel, int id, String tag) {
- Intent intent = new Intent(Intent.ACTION_MAIN)
- .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES)
- .setPackage(packageName);
- final List<ResolveInfo> resolveInfos = pm.queryIntentActivities(
- intent,
- PackageManager.MATCH_DEFAULT_ONLY
- );
- if (resolveInfos == null || resolveInfos.size() == 0 || resolveInfos.get(0) == null) {
- return null;
+ private void updateToggleActions() {
+ ImageButton favorite = findViewById(R.id.fave);
+ if (mNotificationChannel.isImportantConversation()) {
+ favorite.setContentDescription(
+ mContext.getString(R.string.notification_conversation_favorite));
+ favorite.setImageResource(R.drawable.ic_important);
+ } else {
+ favorite.setContentDescription(
+ mContext.getString(R.string.notification_conversation_unfavorite));
+ favorite.setImageResource(R.drawable.ic_important_outline);
}
- final ActivityInfo activityInfo = resolveInfos.get(0).activityInfo;
- intent.setClassName(activityInfo.packageName, activityInfo.name);
- if (channel != null) {
- intent.putExtra(Notification.EXTRA_CHANNEL_ID, channel.getId());
+
+ ImageButton mute = findViewById(R.id.mute);
+ if (mNotificationChannel.getImportance() >= IMPORTANCE_DEFAULT
+ || mNotificationChannel.getImportance() == IMPORTANCE_UNSPECIFIED) {
+ mute.setContentDescription(
+ mContext.getString(R.string.notification_conversation_unmute));
+ mute.setImageResource(R.drawable.ic_notifications_alert);
+ } else {
+ mute.setContentDescription(
+ mContext.getString(R.string.notification_conversation_mute));
+ mute.setImageResource(R.drawable.ic_notifications_silence);
}
- intent.putExtra(Notification.EXTRA_NOTIFICATION_ID, id);
- intent.putExtra(Notification.EXTRA_NOTIFICATION_TAG, tag);
- return intent;
+ }
+
+ private void updateChannel() {
+ Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
+ bgHandler.post(
+ new UpdateChannelRunnable(mINotificationManager, mPackageName,
+ mAppUid, mSelectedAction, mNotificationChannel));
+ mVisualStabilityManager.temporarilyAllowReordering();
}
/**
@@ -556,11 +522,7 @@ public class NotificationConversationInfo extends LinearLayout implements
@Override
public boolean handleCloseControls(boolean save, boolean force) {
if (save && mSelectedAction > -1) {
- Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
- bgHandler.post(
- new UpdateChannelRunnable(mINotificationManager, mPackageName,
- mAppUid, mSelectedAction, mNotificationChannel));
- mVisualStabilityManager.temporarilyAllowReordering();
+ updateChannel();
}
return false;
}
@@ -575,19 +537,7 @@ public class NotificationConversationInfo extends LinearLayout implements
return false;
}
- static class UpdateChannelRunnable implements Runnable {
-
- @Retention(SOURCE)
- @IntDef({ACTION_BUBBLE, ACTION_HOME, ACTION_FAVORITE, ACTION_SNOOZE, ACTION_MUTE,
- ACTION_DEMOTE})
- private @interface Action {}
- static final int ACTION_BUBBLE = 0;
- static final int ACTION_HOME = 1;
- static final int ACTION_FAVORITE = 2;
- static final int ACTION_SNOOZE = 3;
- static final int ACTION_MUTE = 4;
- static final int ACTION_DEMOTE = 5;
- static final int ACTION_UNBUBBLE = 6;
+ class UpdateChannelRunnable implements Runnable {
private final INotificationManager mINotificationManager;
private final String mAppPkg;
@@ -633,10 +583,6 @@ public class NotificationConversationInfo extends LinearLayout implements
mChannelToUpdate.getOriginalImportance(), IMPORTANCE_DEFAULT));
}
break;
- case ACTION_DEMOTE:
- mChannelToUpdate.setDemoted(!mChannelToUpdate.isDemoted());
- break;
-
}
if (channelSettingChanged) {
@@ -646,13 +592,7 @@ public class NotificationConversationInfo extends LinearLayout implements
} catch (RemoteException e) {
Log.e(TAG, "Unable to update notification channel", e);
}
+ ThreadUtils.postOnMainThread(() -> updateToggleActions());
}
}
-
- @Retention(SOURCE)
- @IntDef({BEHAVIOR_ALERTING, BEHAVIOR_SILENT, BEHAVIOR_BUBBLE})
- private @interface AlertingBehavior {}
- private static final int BEHAVIOR_ALERTING = 0;
- private static final int BEHAVIOR_SILENT = 1;
- private static final int BEHAVIOR_BUBBLE = 2;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 6789c814dcee..352abcfc9214 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -58,6 +58,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -384,6 +385,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
guts.resetFalsingCheck();
mOnSettingsClickListener.onSettingsClick(sbn.getKey());
startAppNotificationSettingsActivity(packageName, appUid, channel, row);
+ notificationInfoView.closeControls(v, false);
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
index 44a320419309..745843deeddb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
@@ -375,6 +375,7 @@ public class EdgeBackGestureHandler implements DisplayListener,
mDownPoint.set(ev.getX(), ev.getY());
mThresholdCrossed = false;
}
+
} else if (mAllowGesture) {
if (!mThresholdCrossed) {
if (action == MotionEvent.ACTION_POINTER_DOWN) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 9e64748f2e65..3f5215e1a639 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -269,6 +269,17 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback
}
};
+ private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
+ new DeviceConfig.OnPropertiesChangedListener() {
+ @Override
+ public void onPropertiesChanged(DeviceConfig.Properties properties) {
+ if (properties.getKeyset().contains(NAV_BAR_HANDLE_FORCE_OPAQUE)) {
+ mForceNavBarHandleOpaque = properties.getBoolean(
+ NAV_BAR_HANDLE_FORCE_OPAQUE, /* defaultValue = */ true);
+ }
+ }
+ };
+
@Inject
public NavigationBarFragment(AccessibilityManagerWrapper accessibilityManagerWrapper,
DeviceProvisionedController deviceProvisionedController, MetricsLogger metricsLogger,
@@ -298,21 +309,6 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback
mDivider = divider;
mRecentsOptional = recentsOptional;
mHandler = mainHandler;
-
- mForceNavBarHandleOpaque = DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_SYSTEMUI,
- NAV_BAR_HANDLE_FORCE_OPAQUE,
- /* defaultValue = */ true);
- DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mHandler::post,
- new DeviceConfig.OnPropertiesChangedListener() {
- @Override
- public void onPropertiesChanged(DeviceConfig.Properties properties) {
- if (properties.getKeyset().contains(NAV_BAR_HANDLE_FORCE_OPAQUE)) {
- mForceNavBarHandleOpaque = properties.getBoolean(
- NAV_BAR_HANDLE_FORCE_OPAQUE, /* defaultValue = */ true);
- }
- }
- });
}
// ----- Fragment Lifecycle Callbacks -----
@@ -338,6 +334,13 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback
// Respect the latest disabled-flags.
mCommandQueue.recomputeDisableFlags(mDisplayId, false);
+
+ mForceNavBarHandleOpaque = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ NAV_BAR_HANDLE_FORCE_OPAQUE,
+ /* defaultValue = */ true);
+ DeviceConfig.addOnPropertiesChangedListener(
+ DeviceConfig.NAMESPACE_SYSTEMUI, mHandler::post, mOnPropertiesChangedListener);
}
@Override
@@ -346,6 +349,8 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback
mNavigationModeController.removeListener(this);
mAccessibilityManagerWrapper.removeCallback(mAccessibilityListener);
mContentResolver.unregisterContentObserver(mAssistContentObserver);
+
+ DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
index e3bcdc8b0b60..751217f03fa3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -39,6 +39,7 @@ import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
@@ -225,7 +226,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
val control = builderFromInfo(newControlInfo).build()
- controller.loadForComponent(TEST_COMPONENT) {}
+ controller.loadForComponent(TEST_COMPONENT) { _, _ -> Unit }
reset(persistenceWrapper)
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
@@ -293,11 +294,13 @@ class ControlsControllerImplTest : SysuiTestCase() {
var loaded = false
val control = builderFromInfo(TEST_CONTROL_INFO).build()
- controller.loadForComponent(TEST_COMPONENT) {
+ controller.loadForComponent(TEST_COMPONENT) { controls, favorites ->
loaded = true
- assertEquals(1, it.size)
- val controlStatus = it[0]
+ assertEquals(1, controls.size)
+ val controlStatus = controls[0]
assertEquals(ControlStatus(control, false), controlStatus)
+
+ assertTrue(favorites.isEmpty())
}
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
@@ -315,14 +318,17 @@ class ControlsControllerImplTest : SysuiTestCase() {
val control2 = builderFromInfo(TEST_CONTROL_INFO_2).build()
controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
- controller.loadForComponent(TEST_COMPONENT) {
+ controller.loadForComponent(TEST_COMPONENT) { controls, favorites ->
loaded = true
- assertEquals(2, it.size)
- val controlStatus = it.first { it.control.controlId == TEST_CONTROL_ID }
+ assertEquals(2, controls.size)
+ val controlStatus = controls.first { it.control.controlId == TEST_CONTROL_ID }
assertEquals(ControlStatus(control, true), controlStatus)
- val controlStatus2 = it.first { it.control.controlId == TEST_CONTROL_ID_2 }
+ val controlStatus2 = controls.first { it.control.controlId == TEST_CONTROL_ID_2 }
assertEquals(ControlStatus(control2, false), controlStatus2)
+
+ assertEquals(1, favorites.size)
+ assertEquals(TEST_CONTROL_ID, favorites[0])
}
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
@@ -338,13 +344,16 @@ class ControlsControllerImplTest : SysuiTestCase() {
var loaded = false
controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
- controller.loadForComponent(TEST_COMPONENT) {
+ controller.loadForComponent(TEST_COMPONENT) { controls, favorites ->
loaded = true
- assertEquals(1, it.size)
- val controlStatus = it[0]
+ assertEquals(1, controls.size)
+ val controlStatus = controls[0]
assertEquals(TEST_CONTROL_ID, controlStatus.control.controlId)
assertTrue(controlStatus.favorite)
assertTrue(controlStatus.removed)
+
+ assertEquals(1, favorites.size)
+ assertEquals(TEST_CONTROL_ID, favorites[0])
}
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
@@ -361,7 +370,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
val control = builderFromInfo(newControlInfo).build()
- controller.loadForComponent(TEST_COMPONENT) {}
+ controller.loadForComponent(TEST_COMPONENT) { _, _ -> Unit }
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
capture(controlLoadCallbackCaptor))
@@ -483,4 +492,81 @@ class ControlsControllerImplTest : SysuiTestCase() {
assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT))
assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT_2))
}
+
+ @Test
+ fun testGetFavoritesForComponent() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ assertEquals(listOf(TEST_CONTROL_INFO), controller.getFavoritesForComponent(TEST_COMPONENT))
+ }
+
+ @Test
+ fun testGetFavoritesForComponent_otherComponent() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true)
+ assertTrue(controller.getFavoritesForComponent(TEST_COMPONENT).isEmpty())
+ }
+
+ @Test
+ fun testGetFavoritesForComponent_multipleInOrder() {
+ val controlInfo = ControlInfo(TEST_COMPONENT, "id", "title", 0)
+
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ controller.changeFavoriteStatus(controlInfo, true)
+
+ assertEquals(listOf(TEST_CONTROL_INFO, controlInfo),
+ controller.getFavoritesForComponent(TEST_COMPONENT))
+
+ controller.clearFavorites()
+
+ controller.changeFavoriteStatus(controlInfo, true)
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+ assertEquals(listOf(controlInfo, TEST_CONTROL_INFO),
+ controller.getFavoritesForComponent(TEST_COMPONENT))
+ }
+
+ @Test
+ fun testReplaceFavoritesForComponent_noFavorites() {
+ controller.replaceFavoritesForComponent(TEST_COMPONENT, listOf(TEST_CONTROL_INFO))
+
+ assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT))
+ assertEquals(listOf(TEST_CONTROL_INFO), controller.getFavoritesForComponent(TEST_COMPONENT))
+ }
+
+ @Test
+ fun testReplaceFavoritesForComponent_differentComponentsAreFilteredOut() {
+ controller.replaceFavoritesForComponent(TEST_COMPONENT,
+ listOf(TEST_CONTROL_INFO, TEST_CONTROL_INFO_2))
+
+ assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT))
+ assertEquals(listOf(TEST_CONTROL_INFO), controller.getFavoritesForComponent(TEST_COMPONENT))
+ }
+
+ @Test
+ fun testReplaceFavoritesForComponent_oldFavoritesRemoved() {
+ val controlInfo = ControlInfo(TEST_COMPONENT, "id", "title", 0)
+ assertNotEquals(TEST_CONTROL_INFO, controlInfo)
+
+ controller.changeFavoriteStatus(controlInfo, true)
+ controller.replaceFavoritesForComponent(TEST_COMPONENT, listOf(TEST_CONTROL_INFO))
+
+ assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT))
+ assertEquals(listOf(TEST_CONTROL_INFO), controller.getFavoritesForComponent(TEST_COMPONENT))
+ }
+
+ @Test
+ fun testReplaceFavoritesForComponent_favoritesInOrder() {
+ val controlInfo = ControlInfo(TEST_COMPONENT, "id", "title", 0)
+
+ val listOrder1 = listOf(TEST_CONTROL_INFO, controlInfo)
+ controller.replaceFavoritesForComponent(TEST_COMPONENT, listOrder1)
+
+ assertEquals(2, controller.countFavoritesForComponent(TEST_COMPONENT))
+ assertEquals(listOrder1, controller.getFavoritesForComponent(TEST_COMPONENT))
+
+ val listOrder2 = listOf(controlInfo, TEST_CONTROL_INFO)
+ controller.replaceFavoritesForComponent(TEST_COMPONENT, listOrder2)
+
+ assertEquals(2, controller.countFavoritesForComponent(TEST_COMPONENT))
+ assertEquals(listOrder2, controller.getFavoritesForComponent(TEST_COMPONENT))
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoriteModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoriteModelTest.kt
new file mode 100644
index 000000000000..9ffc29e0eb7e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoriteModelTest.kt
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.management
+
+import android.app.PendingIntent
+import android.service.controls.Control
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlStatus
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+open class FavoriteModelTest : SysuiTestCase() {
+
+ @Mock
+ lateinit var pendingIntent: PendingIntent
+ @Mock
+ lateinit var allAdapter: ControlAdapter
+ @Mock
+ lateinit var favoritesAdapter: ControlAdapter
+
+ val idPrefix = "controlId"
+ val favoritesIndices = listOf(7, 3, 1, 9)
+ val favoritesList = favoritesIndices.map { "controlId$it" }
+ lateinit var controls: List<ControlStatus>
+
+ lateinit var model: FavoriteModel
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ // controlId0 --> zone = 0
+ // controlId1 --> zone = 1, favorite
+ // controlId2 --> zone = 2
+ // controlId3 --> zone = 0, favorite
+ // controlId4 --> zone = 1
+ // controlId5 --> zone = 2
+ // controlId6 --> zone = 0
+ // controlId7 --> zone = 1, favorite
+ // controlId8 --> zone = 2
+ // controlId9 --> zone = 0, favorite
+ controls = (0..9).map {
+ ControlStatus(
+ Control.StatelessBuilder("$idPrefix$it", pendingIntent)
+ .setZone((it % 3).toString())
+ .build(),
+ it in favoritesIndices
+ )
+ }
+
+ model = FavoriteModel(controls, favoritesList, favoritesAdapter, allAdapter)
+ }
+}
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class FavoriteModelNonParametrizedTests : FavoriteModelTest() {
+ @Test
+ fun testAll() {
+ // Zones are sorted alphabetically
+ val expected = listOf(
+ ZoneNameWrapper("0"),
+ ControlWrapper(controls[0]),
+ ControlWrapper(controls[3]),
+ ControlWrapper(controls[6]),
+ ControlWrapper(controls[9]),
+ ZoneNameWrapper("1"),
+ ControlWrapper(controls[1]),
+ ControlWrapper(controls[4]),
+ ControlWrapper(controls[7]),
+ ZoneNameWrapper("2"),
+ ControlWrapper(controls[2]),
+ ControlWrapper(controls[5]),
+ ControlWrapper(controls[8])
+ )
+ assertEquals(expected, model.all)
+ }
+
+ @Test
+ fun testFavoritesInOrder() {
+ val expected = favoritesIndices.map { ControlWrapper(controls[it]) }
+ assertEquals(expected, model.favorites)
+ }
+
+ @Test
+ fun testChangeFavoriteStatus_addFavorite() {
+ val controlToAdd = 6
+ model.changeFavoriteStatus("$idPrefix$controlToAdd", true)
+
+ val pair = model.all.findControl(controlToAdd)
+ pair?.let {
+ assertTrue(it.second.favorite)
+ assertEquals(it.second, model.favorites.last().controlStatus)
+ verify(favoritesAdapter).notifyItemInserted(model.favorites.size - 1)
+ verify(allAdapter).notifyItemChanged(it.first)
+ verifyNoMoreInteractions(favoritesAdapter, allAdapter)
+ } ?: run {
+ fail("control not found")
+ }
+ }
+
+ @Test
+ fun testChangeFavoriteStatus_removeFavorite() {
+ val controlToRemove = 3
+ model.changeFavoriteStatus("$idPrefix$controlToRemove", false)
+
+ val pair = model.all.findControl(controlToRemove)
+ pair?.let {
+ assertFalse(it.second.favorite)
+ assertTrue(model.favorites.none {
+ it.controlStatus.control.controlId == "$idPrefix$controlToRemove"
+ })
+ verify(favoritesAdapter).notifyItemRemoved(favoritesIndices.indexOf(controlToRemove))
+ verify(allAdapter).notifyItemChanged(it.first)
+ verifyNoMoreInteractions(favoritesAdapter, allAdapter)
+ } ?: run {
+ fail("control not found")
+ }
+ }
+
+ @Test
+ fun testChangeFavoriteStatus_sameStatus() {
+ model.changeFavoriteStatus("${idPrefix}7", true)
+ model.changeFavoriteStatus("${idPrefix}6", false)
+
+ val expected = favoritesIndices.map { ControlWrapper(controls[it]) }
+ assertEquals(expected, model.favorites)
+
+ verifyNoMoreInteractions(favoritesAdapter, allAdapter)
+ }
+
+ private fun List<ElementWrapper>.findControl(controlIndex: Int): Pair<Int, ControlStatus>? {
+ val index = indexOfFirst {
+ it is ControlWrapper &&
+ it.controlStatus.control.controlId == "$idPrefix$controlIndex"
+ }
+ return if (index == -1) null else index to (get(index) as ControlWrapper).controlStatus
+ }
+}
+
+@SmallTest
+@RunWith(Parameterized::class)
+class FavoriteModelParameterizedTest(val from: Int, val to: Int) : FavoriteModelTest() {
+
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0} -> {1}")
+ fun data(): Collection<Array<Int>> {
+ return (0..3).flatMap { from ->
+ (0..3).map { to ->
+ arrayOf(from, to)
+ }
+ }.filterNot { it[0] == it[1] }
+ }
+ }
+
+ @Test
+ fun testMoveItem() {
+ val originalFavorites = model.favorites.toList()
+ val originalFavoritesIds =
+ model.favorites.map { it.controlStatus.control.controlId }.toSet()
+ model.onMoveItem(from, to)
+ assertEquals(originalFavorites[from], model.favorites[to])
+ // Check that we still have the same favorites
+ assertEquals(originalFavoritesIds,
+ model.favorites.map { it.controlStatus.control.controlId }.toSet())
+
+ verify(favoritesAdapter).notifyItemMoved(from, to)
+
+ verifyNoMoreInteractions(allAdapter, favoritesAdapter)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
index 399f723f4d62..9117ea8f9fc2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -173,6 +173,21 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
}
@Test
+ public void testPulsing_withoutLightSensor_setsAoDDimmingScrimTransparent() throws Exception {
+ mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
+ null /* sensor */, mBroadcastDispatcher, mDozeHost, null /* handler */,
+ DEFAULT_BRIGHTNESS, SENSOR_TO_BRIGHTNESS, SENSOR_TO_OPACITY,
+ true /* debuggable */);
+ mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+ mScreen.transitionTo(INITIALIZED, DOZE);
+ reset(mDozeHost);
+
+ mScreen.transitionTo(DOZE, DOZE_REQUEST_PULSE);
+
+ verify(mDozeHost).setAodDimmingScrim(eq(0f));
+ }
+
+ @Test
public void testDockedAod_usesLightSensor() {
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE_AOD_DOCKED);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index f080d67bfffb..e8de10f7392b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -75,6 +75,7 @@ import com.android.systemui.statusbar.SbnBuilder;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.phone.ShadeController;
import org.junit.Before;
import org.junit.Rule;
@@ -131,6 +132,8 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
private ShortcutManager mShortcutManager;
@Mock
private NotificationGuts mNotificationGuts;
+ @Mock
+ private ShadeController mShadeController;
@Before
public void setUp() throws Exception {
@@ -139,12 +142,12 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
mDependency.injectTestDependency(BubbleController.class, mBubbleController);
+ mDependency.injectTestDependency(ShadeController.class, mShadeController);
// Inflate the layout
final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
mNotificationInfo = (NotificationConversationInfo) layoutInflater.inflate(
R.layout.notification_conversation_info,
null);
- mNotificationInfo.mShowHomeScreen = true;
mNotificationInfo.setGutsParent(mNotificationGuts);
doAnswer((Answer<Object>) invocation -> {
mNotificationInfo.handleCloseControls(true, false);
@@ -173,7 +176,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
when(mShortcutInfo.getShortLabel()).thenReturn("Convo name");
List<ShortcutInfo> shortcuts = Arrays.asList(mShortcutInfo);
when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcuts);
- mImage = mContext.getDrawable(R.drawable.ic_star);
+ mImage = mContext.getDrawable(R.drawable.ic_remove);
when(mLauncherApps.getShortcutBadgedIconDrawable(eq(mShortcutInfo),
anyInt())).thenReturn(mImage);
@@ -333,8 +336,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
true);
final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
assertEquals(GONE, nameView.getVisibility());
- final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider);
- assertEquals(GONE, dividerView.getVisibility());
}
@Test
@@ -364,8 +365,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
assertEquals(VISIBLE, nameView.getVisibility());
assertTrue(nameView.getText().toString().contains("Proxied"));
- final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider);
- assertEquals(VISIBLE, dividerView.getVisibility());
}
@Test
@@ -502,6 +501,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
verify(mShortcutManager, times(1)).requestPinShortcut(mShortcutInfo, null);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
+ verify(mShadeController).animateCollapsePanels();
}
@Test
@@ -644,9 +644,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
true);
- Button fave = mNotificationInfo.findViewById(R.id.fave);
- assertEquals(mContext.getString(R.string.notification_conversation_favorite),
- fave.getText().toString());
+ ImageButton fave = mNotificationInfo.findViewById(R.id.fave);
+ assertEquals(mContext.getString(R.string.notification_conversation_unfavorite),
+ fave.getContentDescription().toString());
fave.performClick();
mTestableLooper.processAllMessages();
@@ -677,9 +677,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
null,
true);
- Button fave = mNotificationInfo.findViewById(R.id.fave);
- assertEquals(mContext.getString(R.string.notification_conversation_unfavorite),
- fave.getText().toString());
+ ImageButton fave = mNotificationInfo.findViewById(R.id.fave);
+ assertEquals(mContext.getString(R.string.notification_conversation_favorite),
+ fave.getContentDescription().toString());
fave.performClick();
mTestableLooper.processAllMessages();
@@ -692,34 +692,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
}
@Test
- public void testDemote() throws Exception {
- mNotificationInfo.bindNotification(
- mShortcutManager,
- mLauncherApps,
- mMockPackageManager,
- mMockINotificationManager,
- mVisualStabilityManager,
- TEST_PACKAGE_NAME,
- mNotificationChannel,
- mEntry,
- null,
- null,
- null,
- true);
-
-
- ImageButton demote = mNotificationInfo.findViewById(R.id.demote);
- demote.performClick();
- mTestableLooper.processAllMessages();
-
- ArgumentCaptor<NotificationChannel> captor =
- ArgumentCaptor.forClass(NotificationChannel.class);
- verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
- anyString(), anyInt(), captor.capture());
- assertTrue(captor.getValue().isDemoted());
- }
-
- @Test
public void testMute_mute() throws Exception {
mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
mConversationChannel.setImportance(IMPORTANCE_DEFAULT);
@@ -738,9 +710,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
null,
true);
- Button mute = mNotificationInfo.findViewById(R.id.mute);
- assertEquals(mContext.getString(R.string.notification_conversation_mute),
- mute.getText().toString());
+ ImageButton mute = mNotificationInfo.findViewById(R.id.mute);
+ assertEquals(mContext.getString(R.string.notification_conversation_unmute),
+ mute.getContentDescription().toString());
mute.performClick();
mTestableLooper.processAllMessages();
@@ -774,9 +746,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
true);
- Button mute = mNotificationInfo.findViewById(R.id.mute);
- assertEquals(mContext.getString(R.string.notification_conversation_unmute),
- mute.getText().toString());
+ ImageButton mute = mNotificationInfo.findViewById(R.id.mute);
+ assertEquals(mContext.getString(R.string.notification_conversation_mute),
+ mute.getContentDescription().toString());
mute.performClick();
mTestableLooper.processAllMessages();
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 54b420191deb..63dd99e215ab 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -244,6 +244,10 @@ message SystemMessage {
// Package: android
NOTE_SOFTAP_AUTO_DISABLED = 58;
+ // Notify the user that their admin has changed location settings.
+ // Package: android
+ NOTE_LOCATION_CHANGED = 59;
+
// ADD_NEW_IDS_ABOVE_THIS_LINE
// Legacy IDs with arbitrary values appear below
// Legacy IDs existed as stable non-conflicting constants prior to the O release
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 565ee63d89ab..75ec4b04eeed 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -1128,8 +1128,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
@Override
public int getWindowIdForLeashToken(@NonNull IBinder token) {
synchronized (mLock) {
- // TODO: Add a method to lookup window ID by given leash token.
- return -1;
+ return mA11yWindowManager.getWindowIdLocked(token);
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 7b495ce19015..ba29bc88f145 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -761,11 +761,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
@Override
- public int addAccessibilityInteractionConnection(IWindow windowToken,
+ public int addAccessibilityInteractionConnection(IWindow windowToken, IBinder leashToken,
IAccessibilityInteractionConnection connection, String packageName,
int userId) throws RemoteException {
return mA11yWindowManager.addAccessibilityInteractionConnection(
- windowToken, connection, packageName, userId);
+ windowToken, leashToken, connection, packageName, userId);
}
@Override
@@ -2643,6 +2643,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
@Override
+ public void associateEmbeddedHierarchy(@NonNull IBinder host, @NonNull IBinder embedded) {
+ synchronized (mLock) {
+ mA11yWindowManager.associateEmbeddedHierarchyLocked(host, embedded);
+ }
+ }
+
+ @Override
+ public void disassociateEmbeddedHierarchy(@NonNull IBinder token) {
+ synchronized (mLock) {
+ mA11yWindowManager.disassociateEmbeddedHierarchyLocked(token);
+ }
+ }
+
+ @Override
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
synchronized (mLock) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
index 96e345ea9b89..d98e31eadb22 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
@@ -443,8 +443,8 @@ public class AccessibilitySecurityPolicy {
return false;
}
}
- // TODO: Check parent windowId if the giving windowId is from embedded view hierarchy.
- if (windowId == mAccessibilityWindowManager.getActiveWindowId(userId)) {
+ if (mAccessibilityWindowManager.resolveParentWindowIdLocked(windowId)
+ == mAccessibilityWindowManager.getActiveWindowId(userId)) {
return true;
}
return mAccessibilityWindowManager.findA11yWindowInfoByIdLocked(windowId) != null;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index a6041e0ee91c..8c0058130510 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -31,6 +31,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
@@ -100,6 +101,19 @@ public class AccessibilityWindowManager {
new SparseArray<>();
/**
+ * Map of host view and embedded hierarchy, mapping from leash token of its ViewRootImpl.
+ * The key is the token from embedded hierarchy, and the value is the token from its host.
+ */
+ private final ArrayMap<IBinder, IBinder> mHostEmbeddedMap = new ArrayMap<>();
+
+ /**
+ * Map of window id and view hierarchy.
+ * The key is the window id when the ViewRootImpl register to accessibility, and the value is
+ * its leash token.
+ */
+ private final SparseArray<IBinder> mWindowIdMap = new SparseArray<>();
+
+ /**
* This class implements {@link WindowManagerInternal.WindowsForAccessibilityCallback} to
* receive {@link WindowInfo}s from window manager when there's an accessibility change in
* window and holds window lists information per display.
@@ -913,19 +927,21 @@ public class AccessibilityWindowManager {
* Adds accessibility interaction connection according to given window token, package name and
* window token.
*
- * @param windowToken The window token of accessibility interaction connection
+ * @param window The window token of accessibility interaction connection
+ * @param leashToken The leash token of accessibility interaction connection
* @param connection The accessibility interaction connection
* @param packageName The package name
* @param userId The userId
* @return The windowId of added connection
* @throws RemoteException
*/
- public int addAccessibilityInteractionConnection(@NonNull IWindow windowToken,
- @NonNull IAccessibilityInteractionConnection connection, @NonNull String packageName,
- int userId) throws RemoteException {
+ public int addAccessibilityInteractionConnection(@NonNull IWindow window,
+ @NonNull IBinder leashToken, @NonNull IAccessibilityInteractionConnection connection,
+ @NonNull String packageName, int userId) throws RemoteException {
final int windowId;
boolean shouldComputeWindows = false;
- final int displayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken.asBinder());
+ final IBinder token = window.asBinder();
+ final int displayId = mWindowManagerInternal.getDisplayIdForWindow(token);
synchronized (mLock) {
// We treat calls from a profile as if made by its parent as profiles
// share the accessibility state of the parent. The call below
@@ -947,35 +963,33 @@ public class AccessibilityWindowManager {
windowId, connection, packageName, resolvedUid, UserHandle.USER_ALL);
wrapper.linkToDeath();
mGlobalInteractionConnections.put(windowId, wrapper);
- mGlobalWindowTokens.put(windowId, windowToken.asBinder());
+ mGlobalWindowTokens.put(windowId, token);
if (DEBUG) {
Slog.i(LOG_TAG, "Added global connection for pid:" + Binder.getCallingPid()
- + " with windowId: " + windowId + " and token: "
- + windowToken.asBinder());
+ + " with windowId: " + windowId + " and token: " + token);
}
} else {
RemoteAccessibilityConnection wrapper = new RemoteAccessibilityConnection(
windowId, connection, packageName, resolvedUid, resolvedUserId);
wrapper.linkToDeath();
getInteractionConnectionsForUserLocked(resolvedUserId).put(windowId, wrapper);
- getWindowTokensForUserLocked(resolvedUserId).put(windowId, windowToken.asBinder());
+ getWindowTokensForUserLocked(resolvedUserId).put(windowId, token);
if (DEBUG) {
Slog.i(LOG_TAG, "Added user connection for pid:" + Binder.getCallingPid()
- + " with windowId: " + windowId
- + " and token: " + windowToken.asBinder());
+ + " with windowId: " + windowId + " and token: " + token);
}
}
if (isTrackingWindowsLocked(displayId)) {
shouldComputeWindows = true;
}
+ registerIdLocked(leashToken, windowId);
}
if (shouldComputeWindows) {
mWindowManagerInternal.computeWindowsForAccessibility(displayId);
}
- mWindowManagerInternal.setAccessibilityIdToSurfaceMetadata(
- windowToken.asBinder(), windowId);
+ mWindowManagerInternal.setAccessibilityIdToSurfaceMetadata(token, windowId);
return windowId;
}
@@ -1098,6 +1112,7 @@ public class AccessibilityWindowManager {
* Invoked when accessibility interaction connection of window is removed.
*
* @param windowId Removed windowId
+ * @param binder Removed window token
*/
private void onAccessibilityInteractionConnectionRemovedLocked(
int windowId, @Nullable IBinder binder) {
@@ -1110,6 +1125,7 @@ public class AccessibilityWindowManager {
mWindowManagerInternal.setAccessibilityIdToSurfaceMetadata(
binder, AccessibilityWindowInfo.UNDEFINED_WINDOW_ID);
}
+ unregisterIdLocked(windowId);
}
/**
@@ -1132,7 +1148,7 @@ public class AccessibilityWindowManager {
* Returns the userId that owns the given window token, {@link UserHandle#USER_NULL}
* if not found.
*
- * @param windowToken The winodw token
+ * @param windowToken The window token
* @return The userId
*/
public int getWindowOwnerUserId(@NonNull IBinder windowToken) {
@@ -1161,6 +1177,50 @@ public class AccessibilityWindowManager {
}
/**
+ * Establish the relationship between the host and the embedded view hierarchy.
+ *
+ * @param host The token of host hierarchy
+ * @param embedded The token of the embedded hierarchy
+ */
+ public void associateEmbeddedHierarchyLocked(@NonNull IBinder host, @NonNull IBinder embedded) {
+ // Use embedded window as key, since one host window may have multiple embedded windows.
+ associateLocked(embedded, host);
+ }
+
+ /**
+ * Clear the relationship by given token.
+ *
+ * @param token The token
+ */
+ public void disassociateEmbeddedHierarchyLocked(@NonNull IBinder token) {
+ disassociateLocked(token);
+ }
+
+ /**
+ * Gets the parent windowId of the window according to the specified windowId.
+ *
+ * @param windowId The windowId to check
+ * @return The windowId of the parent window, or self if no parent exists
+ */
+ public int resolveParentWindowIdLocked(int windowId) {
+ final IBinder token = getTokenLocked(windowId);
+ if (token == null) {
+ return windowId;
+ }
+ final IBinder resolvedToken = resolveTopParentTokenLocked(token);
+ final int resolvedWindowId = getWindowIdLocked(resolvedToken);
+ return resolvedWindowId != -1 ? resolvedWindowId : windowId;
+ }
+
+ private IBinder resolveTopParentTokenLocked(IBinder token) {
+ final IBinder hostToken = getHostTokenLocked(token);
+ if (hostToken == null) {
+ return token;
+ }
+ return resolveTopParentTokenLocked(hostToken);
+ }
+
+ /**
* Computes partial interactive region of given windowId.
*
* @param windowId The windowId
@@ -1357,6 +1417,7 @@ public class AccessibilityWindowManager {
*/
@Nullable
public AccessibilityWindowInfo findA11yWindowInfoByIdLocked(int windowId) {
+ windowId = resolveParentWindowIdLocked(windowId);
final DisplayWindowsObserver observer = getDisplayWindowObserverByWindowIdLocked(windowId);
if (observer != null) {
return observer.findA11yWindowInfoByIdLocked(windowId);
@@ -1584,6 +1645,88 @@ public class AccessibilityWindowManager {
}
/**
+ * Associate the token of the embedded view hierarchy to the host view hierarchy.
+ *
+ * @param embedded The leash token from the view root of embedded hierarchy
+ * @param host The leash token from the view root of host hierarchy
+ */
+ void associateLocked(IBinder embedded, IBinder host) {
+ mHostEmbeddedMap.put(embedded, host);
+ }
+
+ /**
+ * Clear the relationship of given token.
+ *
+ * @param token The leash token
+ */
+ void disassociateLocked(IBinder token) {
+ mHostEmbeddedMap.remove(token);
+ for (int i = mHostEmbeddedMap.size() - 1; i >= 0; i--) {
+ if (mHostEmbeddedMap.valueAt(i).equals(token)) {
+ mHostEmbeddedMap.removeAt(i);
+ }
+ }
+ }
+
+ /**
+ * Register the leash token with its windowId.
+ *
+ * @param token The token.
+ * @param windowId The windowID.
+ */
+ void registerIdLocked(IBinder token, int windowId) {
+ mWindowIdMap.put(windowId, token);
+ }
+
+ /**
+ * Unregister the windowId and also disassociate its token.
+ *
+ * @param windowId The windowID
+ */
+ void unregisterIdLocked(int windowId) {
+ final IBinder token = mWindowIdMap.get(windowId);
+ if (token == null) {
+ return;
+ }
+ disassociateLocked(token);
+ mWindowIdMap.remove(windowId);
+ }
+
+ /**
+ * Get the leash token by given windowID.
+ *
+ * @param windowId The windowID.
+ * @return The token, or {@code NULL} if this windowID doesn't exist
+ */
+ IBinder getTokenLocked(int windowId) {
+ return mWindowIdMap.get(windowId);
+ }
+
+ /**
+ * Get the windowId by given leash token.
+ *
+ * @param token The token
+ * @return The windowID, or -1 if the token doesn't exist
+ */
+ int getWindowIdLocked(IBinder token) {
+ final int index = mWindowIdMap.indexOfValue(token);
+ if (index == -1) {
+ return index;
+ }
+ return mWindowIdMap.keyAt(index);
+ }
+
+ /**
+ * Get the leash token of the host hierarchy by given token.
+ *
+ * @param token The token
+ * @return The token of host hierarchy, or {@code NULL} if no host exists
+ */
+ IBinder getHostTokenLocked(IBinder token) {
+ return mHostEmbeddedMap.get(token);
+ }
+
+ /**
* Dumps all {@link AccessibilityWindowInfo}s here.
*/
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
index 5844f9873001..1c4db1214d3b 100644
--- a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
@@ -18,12 +18,14 @@ package com.android.server.appprediction;
import static android.Manifest.permission.MANAGE_APP_PREDICTIONS;
import static android.Manifest.permission.PACKAGE_USAGE_STATS;
+import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.content.Context.APP_PREDICTION_SERVICE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.ActivityManagerInternal;
import android.app.prediction.AppPredictionContext;
import android.app.prediction.AppPredictionSessionId;
import android.app.prediction.AppTargetEvent;
@@ -34,7 +36,6 @@ import android.content.pm.ParceledListSlice;
import android.os.Binder;
import android.os.ResultReceiver;
import android.os.ShellCallback;
-import android.os.UserHandle;
import android.util.Slog;
import com.android.server.LocalServices;
@@ -108,21 +109,21 @@ public class AppPredictionManagerService extends
@Override
public void createPredictionSession(@NonNull AppPredictionContext context,
@NonNull AppPredictionSessionId sessionId) {
- runForUserLocked("createPredictionSession",
+ runForUserLocked("createPredictionSession", sessionId,
(service) -> service.onCreatePredictionSessionLocked(context, sessionId));
}
@Override
public void notifyAppTargetEvent(@NonNull AppPredictionSessionId sessionId,
@NonNull AppTargetEvent event) {
- runForUserLocked("notifyAppTargetEvent",
+ runForUserLocked("notifyAppTargetEvent", sessionId,
(service) -> service.notifyAppTargetEventLocked(sessionId, event));
}
@Override
public void notifyLaunchLocationShown(@NonNull AppPredictionSessionId sessionId,
@NonNull String launchLocation, @NonNull ParceledListSlice targetIds) {
- runForUserLocked("notifyLaunchLocationShown", (service) ->
+ runForUserLocked("notifyLaunchLocationShown", sessionId, (service) ->
service.notifyLaunchLocationShownLocked(sessionId, launchLocation, targetIds));
}
@@ -130,32 +131,32 @@ public class AppPredictionManagerService extends
public void sortAppTargets(@NonNull AppPredictionSessionId sessionId,
@NonNull ParceledListSlice targets,
IPredictionCallback callback) {
- runForUserLocked("sortAppTargets",
+ runForUserLocked("sortAppTargets", sessionId,
(service) -> service.sortAppTargetsLocked(sessionId, targets, callback));
}
@Override
public void registerPredictionUpdates(@NonNull AppPredictionSessionId sessionId,
@NonNull IPredictionCallback callback) {
- runForUserLocked("registerPredictionUpdates",
+ runForUserLocked("registerPredictionUpdates", sessionId,
(service) -> service.registerPredictionUpdatesLocked(sessionId, callback));
}
public void unregisterPredictionUpdates(@NonNull AppPredictionSessionId sessionId,
@NonNull IPredictionCallback callback) {
- runForUserLocked("unregisterPredictionUpdates",
+ runForUserLocked("unregisterPredictionUpdates", sessionId,
(service) -> service.unregisterPredictionUpdatesLocked(sessionId, callback));
}
@Override
public void requestPredictionUpdate(@NonNull AppPredictionSessionId sessionId) {
- runForUserLocked("requestPredictionUpdate",
+ runForUserLocked("requestPredictionUpdate", sessionId,
(service) -> service.requestPredictionUpdateLocked(sessionId));
}
@Override
public void onDestroyPredictionSession(@NonNull AppPredictionSessionId sessionId) {
- runForUserLocked("onDestroyPredictionSession",
+ runForUserLocked("onDestroyPredictionSession", sessionId,
(service) -> service.onDestroyPredictionSessionLocked(sessionId));
}
@@ -167,9 +168,12 @@ public class AppPredictionManagerService extends
.exec(this, in, out, err, args, callback, resultReceiver);
}
- private void runForUserLocked(@NonNull String func,
- @NonNull Consumer<AppPredictionPerUserService> c) {
- final int userId = UserHandle.getCallingUserId();
+ private void runForUserLocked(@NonNull final String func,
+ @NonNull final AppPredictionSessionId sessionId,
+ @NonNull final Consumer<AppPredictionPerUserService> c) {
+ ActivityManagerInternal am = LocalServices.getService(ActivityManagerInternal.class);
+ final int userId = am.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ sessionId.getUserId(), false, ALLOW_NON_FULL, null, null);
Context ctx = getContext();
if (!(ctx.checkCallingPermission(PACKAGE_USAGE_STATS) == PERMISSION_GRANTED
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index e3d2dcc8141f..6247a635233a 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -1080,12 +1080,8 @@ public class UserBackupManagerService {
}
}
- public Map<String, Set<String>> getExcludedRestoreKeys(String... packages) {
- return mBackupPreferences.getExcludedRestoreKeysForPackages(packages);
- }
-
- public Map<String, Set<String>> getAllExcludedRestoreKeys() {
- return mBackupPreferences.getAllExcludedRestoreKeys();
+ public Set<String> getExcludedRestoreKeys(String packageName) {
+ return mBackupPreferences.getExcludedRestoreKeysForPackage(packageName);
}
/** Used for generating random salts or passwords. */
@@ -3356,8 +3352,7 @@ public class UserBackupManagerService {
restoreSet,
packageName,
token,
- listener,
- getExcludedRestoreKeys(packageName));
+ listener);
mBackupHandler.sendMessage(msg);
} catch (Exception e) {
// Calling into the transport broke; back off and proceed with the installation.
diff --git a/services/backup/java/com/android/server/backup/UserBackupPreferences.java b/services/backup/java/com/android/server/backup/UserBackupPreferences.java
index 41b9719d273b..bb8bf52187c5 100644
--- a/services/backup/java/com/android/server/backup/UserBackupPreferences.java
+++ b/services/backup/java/com/android/server/backup/UserBackupPreferences.java
@@ -48,16 +48,7 @@ public class UserBackupPreferences {
mEditor.commit();
}
- Map<String, Set<String>> getExcludedRestoreKeysForPackages(String... packages) {
- Map<String, Set<String>> excludedKeys = new HashMap<>();
- for (String packageName : packages) {
- excludedKeys.put(packageName,
- mPreferences.getStringSet(packageName, Collections.emptySet()));
- }
- return excludedKeys;
- }
-
- Map<String, Set<String>> getAllExcludedRestoreKeys() {
- return (Map<String, Set<String>>) mPreferences.getAll();
+ Set<String> getExcludedRestoreKeysForPackage(String packageName) {
+ return mPreferences.getStringSet(packageName, Collections.emptySet());
}
}
diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
index 05396f36b364..87a8e4982529 100644
--- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java
+++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
@@ -299,8 +299,7 @@ public class BackupHandler extends Handler {
params.pmToken,
params.isSystemRestore,
params.filterSet,
- params.listener,
- params.excludedKeys);
+ params.listener);
synchronized (backupManagerService.getPendingRestores()) {
if (backupManagerService.isRestoreInProgress()) {
diff --git a/services/backup/java/com/android/server/backup/params/RestoreParams.java b/services/backup/java/com/android/server/backup/params/RestoreParams.java
index 09b7e3535e2e..a6fea6cc75a0 100644
--- a/services/backup/java/com/android/server/backup/params/RestoreParams.java
+++ b/services/backup/java/com/android/server/backup/params/RestoreParams.java
@@ -37,7 +37,6 @@ public class RestoreParams {
public final boolean isSystemRestore;
@Nullable public final String[] filterSet;
public final OnTaskFinishedListener listener;
- public final Map<String, Set<String>> excludedKeys;
/**
* No kill after restore.
@@ -48,8 +47,7 @@ public class RestoreParams {
IBackupManagerMonitor monitor,
long token,
PackageInfo packageInfo,
- OnTaskFinishedListener listener,
- Map<String, Set<String>> excludedKeys) {
+ OnTaskFinishedListener listener) {
return new RestoreParams(
transportClient,
observer,
@@ -59,8 +57,7 @@ public class RestoreParams {
/* pmToken */ 0,
/* isSystemRestore */ false,
/* filterSet */ null,
- listener,
- excludedKeys);
+ listener);
}
/**
@@ -73,8 +70,7 @@ public class RestoreParams {
long token,
String packageName,
int pmToken,
- OnTaskFinishedListener listener,
- Map<String, Set<String>> excludedKeys) {
+ OnTaskFinishedListener listener) {
String[] filterSet = {packageName};
return new RestoreParams(
transportClient,
@@ -85,8 +81,7 @@ public class RestoreParams {
pmToken,
/* isSystemRestore */ false,
filterSet,
- listener,
- excludedKeys);
+ listener);
}
/**
@@ -97,8 +92,7 @@ public class RestoreParams {
IRestoreObserver observer,
IBackupManagerMonitor monitor,
long token,
- OnTaskFinishedListener listener,
- Map<String, Set<String>> excludedKeys) {
+ OnTaskFinishedListener listener) {
return new RestoreParams(
transportClient,
observer,
@@ -108,8 +102,7 @@ public class RestoreParams {
/* pmToken */ 0,
/* isSystemRestore */ true,
/* filterSet */ null,
- listener,
- excludedKeys);
+ listener);
}
/**
@@ -122,8 +115,7 @@ public class RestoreParams {
long token,
String[] filterSet,
boolean isSystemRestore,
- OnTaskFinishedListener listener,
- Map<String, Set<String>> excludedKeys) {
+ OnTaskFinishedListener listener) {
return new RestoreParams(
transportClient,
observer,
@@ -133,8 +125,7 @@ public class RestoreParams {
/* pmToken */ 0,
isSystemRestore,
filterSet,
- listener,
- excludedKeys);
+ listener);
}
private RestoreParams(
@@ -146,8 +137,7 @@ public class RestoreParams {
int pmToken,
boolean isSystemRestore,
@Nullable String[] filterSet,
- OnTaskFinishedListener listener,
- Map<String, Set<String>> excludedKeys) {
+ OnTaskFinishedListener listener) {
this.transportClient = transportClient;
this.observer = observer;
this.monitor = monitor;
@@ -157,6 +147,5 @@ public class RestoreParams {
this.isSystemRestore = isSystemRestore;
this.filterSet = filterSet;
this.listener = listener;
- this.excludedKeys = excludedKeys;
}
}
diff --git a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
index c0f76c39e04f..5a57cdc39402 100644
--- a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
+++ b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
@@ -178,8 +178,7 @@ public class ActiveRestoreSession extends IRestoreSession.Stub {
observer,
monitor,
token,
- listener,
- mBackupManagerService.getAllExcludedRestoreKeys()),
+ listener),
"RestoreSession.restoreAll()");
} finally {
Binder.restoreCallingIdentity(oldId);
@@ -272,8 +271,7 @@ public class ActiveRestoreSession extends IRestoreSession.Stub {
token,
packages,
/* isSystemRestore */ packages.length > 1,
- listener,
- mBackupManagerService.getExcludedRestoreKeys(packages)),
+ listener),
"RestoreSession.restorePackages(" + packages.length + " packages)");
} finally {
Binder.restoreCallingIdentity(oldId);
@@ -365,8 +363,7 @@ public class ActiveRestoreSession extends IRestoreSession.Stub {
monitor,
token,
app,
- listener,
- mBackupManagerService.getExcludedRestoreKeys(app.packageName)),
+ listener),
"RestoreSession.restorePackage(" + packageName + ")");
} finally {
Binder.restoreCallingIdentity(oldId);
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index f90d936a5f4d..3c37f737f8be 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -155,8 +155,6 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
// When finished call listener
private final OnTaskFinishedListener mListener;
- private final Map<String, Set<String>> mExcludedKeys;
-
// Key/value: bookkeeping about staged data and files for agent access
private File mBackupDataName;
private File mStageName;
@@ -168,14 +166,14 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
@VisibleForTesting
- PerformUnifiedRestoreTask(Map<String, Set<String>> excludedKeys) {
- mExcludedKeys = excludedKeys;
+ PerformUnifiedRestoreTask(UserBackupManagerService backupManagerService) {
mListener = null;
mAgentTimeoutParameters = null;
mTransportClient = null;
mTransportManager = null;
mEphemeralOpToken = 0;
mUserId = 0;
+ this.backupManagerService = backupManagerService;
}
// This task can assume that the wakelock is properly held for it and doesn't have to worry
@@ -190,8 +188,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
int pmToken,
boolean isFullSystemRestore,
@Nullable String[] filterSet,
- OnTaskFinishedListener listener,
- Map<String, Set<String>> excludedKeys) {
+ OnTaskFinishedListener listener) {
this.backupManagerService = backupManagerService;
mUserId = backupManagerService.getUserId();
mTransportManager = backupManagerService.getTransportManager();
@@ -213,8 +210,6 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
backupManagerService.getAgentTimeoutParameters(),
"Timeout parameters cannot be null");
- mExcludedKeys = excludedKeys;
-
if (targetPackage != null) {
// Single package restore
mAcceptSet = new ArrayList<>();
@@ -791,8 +786,9 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
!getExcludedKeysForPackage(PLATFORM_PACKAGE_NAME).isEmpty();
}
- private Set<String> getExcludedKeysForPackage(String packageName) {
- return mExcludedKeys.getOrDefault(packageName, Collections.emptySet());
+ @VisibleForTesting
+ Set<String> getExcludedKeysForPackage(String packageName) {
+ return backupManagerService.getExcludedRestoreKeys(packageName);
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 63cddac0bfba..2c91a11c358c 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -43,6 +43,7 @@ import android.location.Criteria;
import android.location.GeocoderParams;
import android.location.Geofence;
import android.location.GnssMeasurementCorrections;
+import android.location.GnssRequest;
import android.location.IBatchedLocationCallback;
import android.location.IGnssMeasurementsListener;
import android.location.IGnssNavigationMessageListener;
@@ -2287,12 +2288,14 @@ public class LocationManagerService extends ILocationManager.Stub {
}
@Override
- public boolean addGnssMeasurementsListener(IGnssMeasurementsListener listener,
- String packageName, String featureId, String listenerIdentifier) {
+ public boolean addGnssMeasurementsListener(@Nullable GnssRequest request,
+ IGnssMeasurementsListener listener,
+ String packageName, String featureId,
+ String listenerIdentifier) {
Objects.requireNonNull(listenerIdentifier);
return mGnssManagerService != null && mGnssManagerService.addGnssMeasurementsListener(
- listener, packageName, featureId, listenerIdentifier);
+ request, listener, packageName, featureId, listenerIdentifier);
}
@Override
@@ -2419,6 +2422,17 @@ public class LocationManagerService extends ILocationManager.Stub {
}
@Override
+ public void setLocationEnabledForUser(boolean enabled, int userId) {
+ if (UserHandle.getCallingUserId() != userId) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS,
+ null);
+ }
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS,
+ "Requires WRITE_SECURE_SETTINGS permission");
+ mSettingsHelper.setLocationEnabled(enabled, userId);
+ }
+
+ @Override
public boolean isLocationEnabledForUser(int userId) {
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, false, false, "isLocationEnabledForUser", null);
diff --git a/services/core/java/com/android/server/LocationManagerServiceUtils.java b/services/core/java/com/android/server/LocationManagerServiceUtils.java
index 372e91e41772..ba1c81cdd975 100644
--- a/services/core/java/com/android/server/LocationManagerServiceUtils.java
+++ b/services/core/java/com/android/server/LocationManagerServiceUtils.java
@@ -17,6 +17,7 @@
package com.android.server;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
@@ -37,22 +38,31 @@ public class LocationManagerServiceUtils {
/**
* Listener that can be linked to a binder.
* @param <TListener> listener type
+ * @param <TRequest> request type
*/
- public static class LinkedListener<TListener> extends
+ public static class LinkedListener<TRequest, TListener> extends
LinkedListenerBase {
+ @Nullable protected final TRequest mRequest;
private final TListener mListener;
private final Consumer<TListener> mBinderDeathCallback;
public LinkedListener(
+ @Nullable TRequest request,
@NonNull TListener listener,
String listenerName,
@NonNull CallerIdentity callerIdentity,
@NonNull Consumer<TListener> binderDeathCallback) {
super(callerIdentity, listenerName);
mListener = listener;
+ mRequest = request;
mBinderDeathCallback = binderDeathCallback;
}
+ @Nullable
+ public TRequest getRequest() {
+ return mRequest;
+ }
+
@Override
public void binderDied() {
if (D) Log.d(TAG, "Remote " + mListenerName + " died.");
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index b994e6c58e64..f2d5a9b685f4 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -302,15 +302,19 @@ final class UiModeManagerService extends SystemService {
private final ContentObserver mDarkThemeObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange, Uri uri) {
- int mode = Secure.getIntForUser(getContext().getContentResolver(), Secure.UI_NIGHT_MODE,
- mNightMode, 0);
- if (mode == MODE_NIGHT_AUTO || mode == MODE_NIGHT_CUSTOM) {
- mode = MODE_NIGHT_YES;
- }
- SystemProperties.set(SYSTEM_PROPERTY_DEVICE_THEME, Integer.toString(mode));
+ updateSystemProperties();
}
};
+ private void updateSystemProperties() {
+ int mode = Secure.getIntForUser(getContext().getContentResolver(), Secure.UI_NIGHT_MODE,
+ mNightMode, 0);
+ if (mode == MODE_NIGHT_AUTO || mode == MODE_NIGHT_CUSTOM) {
+ mode = MODE_NIGHT_YES;
+ }
+ SystemProperties.set(SYSTEM_PROPERTY_DEVICE_THEME, Integer.toString(mode));
+ }
+
@Override
public void onSwitchUser(int userHandle) {
super.onSwitchUser(userHandle);
@@ -392,6 +396,7 @@ final class UiModeManagerService extends SystemService {
context.getContentResolver().registerContentObserver(Secure.getUriFor(Secure.UI_NIGHT_MODE),
false, mDarkThemeObserver, 0);
+ updateSystemProperties();
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 0686e3e99b2f..b6dc3de908f0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -466,9 +466,18 @@ public class ActivityManagerService extends IActivityManager.Stub
// How long we wait for a launched process to attach to the activity manager
// before we decide it's never going to come up for real.
static final int PROC_START_TIMEOUT = 10*1000;
+ // How long we wait for an attached process to publish its content providers
+ // before we decide it must be hung.
+ static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10*1000;
+
// How long we wait to kill an application zygote, after the last process using
// it has gone away.
static final int KILL_APP_ZYGOTE_DELAY_MS = 5 * 1000;
+ /**
+ * How long we wait for an provider to be published. Should be longer than
+ * {@link #CONTENT_PROVIDER_PUBLISH_TIMEOUT}.
+ */
+ static final int CONTENT_PROVIDER_WAIT_TIMEOUT = 20 * 1000;
// How long we wait for a launched process to attach to the activity manager
// before we decide it's never going to come up for real, when the process was
@@ -4925,8 +4934,7 @@ public class ActivityManagerService extends IActivityManager.Stub
if (providers != null && checkAppInLaunchingProvidersLocked(app)) {
Message msg = mHandler.obtainMessage(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG);
msg.obj = app;
- mHandler.sendMessageDelayed(msg,
- ContentResolver.CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS);
+ mHandler.sendMessageDelayed(msg, CONTENT_PROVIDER_PUBLISH_TIMEOUT);
}
checkTime(startTime, "attachApplicationLocked: before bindApplication");
@@ -7193,8 +7201,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
// Wait for the provider to be published...
- final long timeout =
- SystemClock.uptimeMillis() + ContentResolver.CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS;
+ final long timeout = SystemClock.uptimeMillis() + CONTENT_PROVIDER_WAIT_TIMEOUT;
boolean timedOut = false;
synchronized (cpr) {
while (cpr.provider == null) {
@@ -7231,14 +7238,12 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
if (timedOut) {
- // Note we do it after releasing the lock.
+ // Note we do it afer releasing the lock.
String callerName = "unknown";
- if (caller != null) {
- synchronized (this) {
- final ProcessRecord record = mProcessList.getLRURecordForAppLocked(caller);
- if (record != null) {
- callerName = record.processName;
- }
+ synchronized (this) {
+ final ProcessRecord record = mProcessList.getLRURecordForAppLocked(caller);
+ if (record != null) {
+ callerName = record.processName;
}
}
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 145f91bdb0b3..789f7199948d 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -798,7 +798,6 @@ class AppErrors {
boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
- final String packageName;
final int userId;
synchronized (mService) {
final ProcessRecord proc = data.proc;
@@ -807,7 +806,6 @@ class AppErrors {
Slog.e(TAG, "handleShowAppErrorUi: proc is null");
return;
}
- packageName = proc.info.packageName;
userId = proc.userId;
if (proc.getDialogController().hasCrashDialogs()) {
Slog.e(TAG, "App already has crash dialog: " + proc);
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index f3d8bc8cb64f..e63da9b1e80b 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1910,8 +1910,7 @@ class ProcessRecord implements WindowProcessListener {
mWaitDialog = null;
}
- void forAllDialogs(List<? extends BaseErrorDialog> dialogs,
- Consumer<BaseErrorDialog> c) {
+ void forAllDialogs(List<? extends BaseErrorDialog> dialogs, Consumer<BaseErrorDialog> c) {
for (int i = dialogs.size() - 1; i >= 0; i--) {
c.accept(dialogs.get(i));
}
@@ -1920,42 +1919,72 @@ class ProcessRecord implements WindowProcessListener {
void showCrashDialogs(AppErrorDialog.Data data) {
List<Context> contexts = getDisplayContexts(false /* lastUsedOnly */);
mCrashDialogs = new ArrayList<>();
-
for (int i = contexts.size() - 1; i >= 0; i--) {
final Context c = contexts.get(i);
mCrashDialogs.add(new AppErrorDialog(c, mService, data));
}
- mService.mUiHandler.post(() -> mCrashDialogs.forEach(Dialog::show));
+ mService.mUiHandler.post(() -> {
+ List<AppErrorDialog> dialogs;
+ synchronized (mService) {
+ dialogs = mCrashDialogs;
+ }
+ if (dialogs != null) {
+ forAllDialogs(dialogs, Dialog::show);
+ }
+ });
}
void showAnrDialogs(AppNotRespondingDialog.Data data) {
List<Context> contexts = getDisplayContexts(isSilentAnr() /* lastUsedOnly */);
mAnrDialogs = new ArrayList<>();
-
for (int i = contexts.size() - 1; i >= 0; i--) {
final Context c = contexts.get(i);
mAnrDialogs.add(new AppNotRespondingDialog(mService, c, data));
}
- mService.mUiHandler.post(() -> mAnrDialogs.forEach(Dialog::show));
+ mService.mUiHandler.post(() -> {
+ List<AppNotRespondingDialog> dialogs;
+ synchronized (mService) {
+ dialogs = mAnrDialogs;
+ }
+ if (dialogs != null) {
+ forAllDialogs(dialogs, Dialog::show);
+ }
+ });
}
void showViolationDialogs(AppErrorResult res) {
List<Context> contexts = getDisplayContexts(false /* lastUsedOnly */);
mViolationDialogs = new ArrayList<>();
-
for (int i = contexts.size() - 1; i >= 0; i--) {
final Context c = contexts.get(i);
mViolationDialogs.add(
new StrictModeViolationDialog(c, mService, res, ProcessRecord.this));
}
- mService.mUiHandler.post(() -> mViolationDialogs.forEach(Dialog::show));
+ mService.mUiHandler.post(() -> {
+ List<StrictModeViolationDialog> dialogs;
+ synchronized (mService) {
+ dialogs = mViolationDialogs;
+ }
+ if (dialogs != null) {
+ forAllDialogs(dialogs, Dialog::show);
+ }
+ });
}
void showDebugWaitingDialogs() {
List<Context> contexts = getDisplayContexts(true /* lastUsedOnly */);
final Context c = contexts.get(0);
mWaitDialog = new AppWaitingForDebuggerDialog(mService, c, ProcessRecord.this);
- mService.mUiHandler.post(() -> mWaitDialog.show());
+
+ mService.mUiHandler.post(() -> {
+ Dialog dialog;
+ synchronized (mService) {
+ dialog = mWaitDialog;
+ }
+ if (dialog != null) {
+ dialog.show();
+ }
+ });
}
/**
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index c75ee04543e3..b2d0441bb2f8 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -414,6 +414,7 @@ class UserController implements Handler.Callback {
Intent intent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED, null);
intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
+ | Intent.FLAG_RECEIVER_OFFLOAD
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
mInjector.broadcastIntent(intent, null, resultTo, 0, null, null,
new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 1f998c377c7b..ef1bc835dea5 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -702,6 +702,10 @@ public class AudioDeviceInventory {
delay = 0;
}
mDeviceBroker.postSetHearingAidConnectionState(state, device, delay);
+ if (state == BluetoothHearingAid.STATE_CONNECTED) {
+ mDeviceBroker.setForceUse_Async(AudioSystem.FOR_MEDIA, AudioSystem.FORCE_NONE,
+ "HEARING_AID set to CONNECTED");
+ }
return delay;
}
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 07fc9b7a7e5f..26c94c5ab978 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -25,6 +25,7 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_IRIS;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
import static android.hardware.biometrics.BiometricManager.Authenticators;
+import android.annotation.IntDef;
import android.app.ActivityManager;
import android.app.IActivityManager;
import android.app.UserSwitchObserver;
@@ -83,6 +84,25 @@ public class BiometricService extends SystemService {
static final String TAG = "BiometricService";
private static final boolean DEBUG = true;
+ private static final int BIOMETRIC_NO_HARDWARE = 0;
+ private static final int BIOMETRIC_OK = 1;
+ private static final int BIOMETRIC_DISABLED_BY_DEVICE_POLICY = 2;
+ private static final int BIOMETRIC_INSUFFICIENT_STRENGTH = 3;
+ private static final int BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE = 4;
+ private static final int BIOMETRIC_HARDWARE_NOT_DETECTED = 5;
+ private static final int BIOMETRIC_NOT_ENROLLED = 6;
+ private static final int BIOMETRIC_NOT_ENABLED_FOR_APPS = 7;
+
+ @IntDef({BIOMETRIC_NO_HARDWARE,
+ BIOMETRIC_OK,
+ BIOMETRIC_DISABLED_BY_DEVICE_POLICY,
+ BIOMETRIC_INSUFFICIENT_STRENGTH,
+ BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE,
+ BIOMETRIC_HARDWARE_NOT_DETECTED,
+ BIOMETRIC_NOT_ENROLLED,
+ BIOMETRIC_NOT_ENABLED_FOR_APPS})
+ @interface BiometricStatus {}
+
private static final int MSG_ON_AUTHENTICATION_SUCCEEDED = 2;
private static final int MSG_ON_AUTHENTICATION_REJECTED = 3;
private static final int MSG_ON_ERROR = 4;
@@ -206,7 +226,7 @@ public class BiometricService extends SystemService {
}
boolean isAllowDeviceCredential() {
- return Utils.isDeviceCredentialAllowed(mBundle);
+ return Utils.isCredentialRequested(mBundle);
}
}
@@ -372,16 +392,20 @@ public class BiometricService extends SystemService {
* strength.
* @return a bitfield, see {@link Authenticators}
*/
- public int getActualStrength() {
+ int getActualStrength() {
return OEMStrength | updatedStrength;
}
+ boolean isDowngraded() {
+ return OEMStrength != updatedStrength;
+ }
+
/**
* Stores the updated strength, which takes effect whenever {@link #getActualStrength()}
* is checked.
* @param newStrength
*/
- public void updateStrength(int newStrength) {
+ void updateStrength(int newStrength) {
String log = "updateStrength: Before(" + toString() + ")";
updatedStrength = newStrength;
log += " After(" + toString() + ")";
@@ -1007,6 +1031,79 @@ public class BiometricService extends SystemService {
return isBiometricDisabled;
}
+ private static int biometricStatusToBiometricConstant(@BiometricStatus int status) {
+ switch (status) {
+ case BIOMETRIC_NO_HARDWARE:
+ return BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT;
+ case BIOMETRIC_OK:
+ return BiometricConstants.BIOMETRIC_SUCCESS;
+ case BIOMETRIC_DISABLED_BY_DEVICE_POLICY:
+ return BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE;
+ case BIOMETRIC_INSUFFICIENT_STRENGTH:
+ return BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT;
+ case BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE:
+ return BiometricConstants.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED;
+ case BIOMETRIC_HARDWARE_NOT_DETECTED:
+ return BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE;
+ case BIOMETRIC_NOT_ENROLLED:
+ return BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS;
+ case BIOMETRIC_NOT_ENABLED_FOR_APPS:
+ return BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE;
+ default:
+ return BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE;
+ }
+ }
+
+ /**
+ * Returns the status of the authenticator, with errors returned in a specific priority order.
+ * For example, {@link #BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE} is only returned
+ * if it has enrollments, and is enabled for apps.
+ *
+ * We should only return the modality if the authenticator should be exposed. e.g.
+ * BIOMETRIC_NOT_ENROLLED_FOR_APPS should not expose the authenticator's type.
+ *
+ * @return A Pair with `first` being modality, and `second` being @BiometricStatus
+ */
+ private Pair<Integer, Integer> getStatusForBiometricAuthenticator(
+ AuthenticatorWrapper authenticator, int userId, String opPackageName,
+ boolean checkDevicePolicyManager, int requestedStrength) {
+ if (checkDevicePolicyManager) {
+ if (isBiometricDisabledByDevicePolicy(authenticator.modality, userId)) {
+ return new Pair<>(TYPE_NONE, BIOMETRIC_DISABLED_BY_DEVICE_POLICY);
+ }
+ }
+
+ final boolean wasStrongEnough =
+ Utils.isAtLeastStrength(authenticator.OEMStrength, requestedStrength);
+ final boolean isStrongEnough =
+ Utils.isAtLeastStrength(authenticator.getActualStrength(), requestedStrength);
+
+ if (wasStrongEnough && !isStrongEnough) {
+ return new Pair<>(authenticator.modality,
+ BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE);
+ } else if (!wasStrongEnough) {
+ return new Pair<>(TYPE_NONE, BIOMETRIC_INSUFFICIENT_STRENGTH);
+ }
+
+ try {
+ if (!authenticator.impl.isHardwareDetected(opPackageName)) {
+ return new Pair<>(authenticator.modality, BIOMETRIC_HARDWARE_NOT_DETECTED);
+ }
+
+ if (!authenticator.impl.hasEnrolledTemplates(userId, opPackageName)) {
+ return new Pair<>(authenticator.modality, BIOMETRIC_NOT_ENROLLED);
+ }
+ } catch (RemoteException e) {
+ return new Pair<>(authenticator.modality, BIOMETRIC_HARDWARE_NOT_DETECTED);
+ }
+
+ if (!isEnabledForApp(authenticator.modality, userId)) {
+ return new Pair<>(TYPE_NONE, BIOMETRIC_NOT_ENABLED_FOR_APPS);
+ }
+
+ return new Pair<>(authenticator.modality, BIOMETRIC_OK);
+ }
+
/**
* Depending on the requested authentication (credential/biometric combination), checks their
* availability.
@@ -1029,10 +1126,9 @@ public class BiometricService extends SystemService {
private Pair<Integer, Integer> checkAndGetAuthenticators(int userId, Bundle bundle,
String opPackageName, boolean checkDevicePolicyManager) throws RemoteException {
- final boolean biometricRequested = Utils.isBiometricAllowed(bundle);
- final boolean credentialRequested = Utils.isDeviceCredentialAllowed(bundle);
+ final boolean biometricRequested = Utils.isBiometricRequested(bundle);
+ final boolean credentialRequested = Utils.isCredentialRequested(bundle);
- final boolean biometricOk;
final boolean credentialOk = mTrustManager.isDeviceSecure(userId);
// Assuming that biometric authenticators are listed in priority-order, the rest of this
@@ -1041,96 +1137,56 @@ public class BiometricService extends SystemService {
// the correct error. Error strings that are modality-specific should also respect the
// priority-order.
- // Find first biometric authenticator that's strong enough, detected, enrolled, and enabled.
- boolean disabledByDevicePolicy = false;
- boolean hasSufficientStrength = false;
- boolean isHardwareDetected = false;
- boolean hasTemplatesEnrolled = false;
- boolean enabledForApps = false;
+ int firstBiometricModality = TYPE_NONE;
+ @BiometricStatus int firstBiometricStatus = BIOMETRIC_NO_HARDWARE;
+
+ int biometricModality = TYPE_NONE;
+ @BiometricStatus int biometricStatus = BIOMETRIC_NO_HARDWARE;
- int modality = TYPE_NONE;
- int firstHwAvailable = TYPE_NONE;
for (AuthenticatorWrapper authenticator : mAuthenticators) {
- final int actualStrength = authenticator.getActualStrength();
final int requestedStrength = Utils.getPublicBiometricStrength(bundle);
+ Pair<Integer, Integer> result = getStatusForBiometricAuthenticator(
+ authenticator, userId, opPackageName, checkDevicePolicyManager,
+ requestedStrength);
- if (isBiometricDisabledByDevicePolicy(authenticator.modality, userId)) {
- disabledByDevicePolicy = true;
- continue;
- }
- disabledByDevicePolicy = false;
-
- if (!Utils.isAtLeastStrength(actualStrength, requestedStrength)) {
- continue;
- }
- hasSufficientStrength = true;
+ biometricStatus = result.second;
- if (!authenticator.impl.isHardwareDetected(opPackageName)) {
- continue;
- }
- isHardwareDetected = true;
+ Slog.d(TAG, "Authenticator ID: " + authenticator.id
+ + " Modality: " + authenticator.modality
+ + " ReportedModality: " + result.first
+ + " Status: " + biometricStatus);
- if (firstHwAvailable == TYPE_NONE) {
- // Store the first one since we want to return the error in correct
- // priority order.
- firstHwAvailable = authenticator.modality;
+ if (firstBiometricModality == TYPE_NONE) {
+ firstBiometricModality = result.first;
+ firstBiometricStatus = biometricStatus;
}
- if (!authenticator.impl.hasEnrolledTemplates(userId, opPackageName)) {
- continue;
- }
- hasTemplatesEnrolled = true;
-
- if (!isEnabledForApp(authenticator.modality, userId)) {
- continue;
+ if (biometricStatus == BIOMETRIC_OK) {
+ biometricModality = result.first;
+ break;
}
- enabledForApps = true;
- modality = authenticator.modality;
- break;
}
- biometricOk = !disabledByDevicePolicy
- && hasSufficientStrength && isHardwareDetected
- && hasTemplatesEnrolled && enabledForApps;
-
- Slog.d(TAG, "checkAndGetAuthenticators: user=" + userId
- + " checkDevicePolicyManager=" + checkDevicePolicyManager
- + " isHardwareDetected=" + isHardwareDetected
- + " hasTemplatesEnrolled=" + hasTemplatesEnrolled
- + " enabledForApps=" + enabledForApps
- + " disabledByDevicePolicy=" + disabledByDevicePolicy);
-
if (biometricRequested && credentialRequested) {
- if (credentialOk || biometricOk) {
- if (!biometricOk) {
+ if (credentialOk || biometricStatus == BIOMETRIC_OK) {
+ if (biometricStatus != BIOMETRIC_OK) {
// If there's a problem with biometrics but device credential is
// allowed, only show credential UI.
bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED,
Authenticators.DEVICE_CREDENTIAL);
}
- return new Pair<>(modality, BiometricConstants.BIOMETRIC_SUCCESS);
+ return new Pair<>(biometricModality, BiometricConstants.BIOMETRIC_SUCCESS);
} else {
- return new Pair<>(firstHwAvailable,
+ return new Pair<>(firstBiometricModality,
BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS);
}
} else if (biometricRequested) {
- if (biometricOk) {
- return new Pair<>(modality, BiometricConstants.BIOMETRIC_SUCCESS);
- } else if (disabledByDevicePolicy) {
- return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE);
- } else if (!hasSufficientStrength) {
- return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT);
- } else if (!isHardwareDetected) {
- return new Pair<>(firstHwAvailable,
- BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE);
- } else if (!hasTemplatesEnrolled) {
- return new Pair<>(firstHwAvailable,
- BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS);
- } else if (!enabledForApps) {
- return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE);
+ if (biometricStatus == BIOMETRIC_OK) {
+ return new Pair<>(biometricModality,
+ biometricStatusToBiometricConstant(biometricStatus));
} else {
- Slog.e(TAG, "Unexpected case");
- return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE);
+ return new Pair<>(firstBiometricModality,
+ biometricStatusToBiometricConstant(firstBiometricStatus));
}
} else if (credentialRequested) {
if (credentialOk) {
@@ -1403,16 +1459,14 @@ public class BiometricService extends SystemService {
return;
}
- if (acquiredInfo != BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) {
- if (message == null) {
- Slog.w(TAG, "Ignoring null message: " + acquiredInfo);
- return;
- }
- try {
- mStatusBarService.onBiometricHelp(message);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- }
+ if (message == null) {
+ Slog.w(TAG, "Ignoring null message: " + acquiredInfo);
+ return;
+ }
+ try {
+ mStatusBarService.onBiometricHelp(message);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
}
}
diff --git a/services/core/java/com/android/server/biometrics/LoggableMonitor.java b/services/core/java/com/android/server/biometrics/LoggableMonitor.java
index c03c77f41f57..c50ab175199d 100644
--- a/services/core/java/com/android/server/biometrics/LoggableMonitor.java
+++ b/services/core/java/com/android/server/biometrics/LoggableMonitor.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.FingerprintManager;
import android.util.Slog;
import com.android.internal.util.FrameworkStatsLog;
@@ -69,8 +70,12 @@ public abstract class LoggableMonitor {
protected final void logOnAcquired(Context context, int acquiredInfo, int vendorCode,
int targetUserId) {
- if (statsModality() == BiometricsProtoEnums.MODALITY_FACE) {
- if (acquiredInfo == FaceManager.FACE_ACQUIRED_START) {
+
+ final boolean isFace = statsModality() == BiometricsProtoEnums.MODALITY_FACE;
+ final boolean isFingerprint = statsModality() == BiometricsProtoEnums.MODALITY_FINGERPRINT;
+ if (isFace || isFingerprint) {
+ if ((isFingerprint && acquiredInfo == FingerprintManager.FINGERPRINT_ACQUIRED_START)
+ || (isFace && acquiredInfo == FaceManager.FACE_ACQUIRED_START)) {
mFirstAcquireTimeMs = System.currentTimeMillis();
}
} else if (acquiredInfo == BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) {
@@ -124,7 +129,7 @@ public abstract class LoggableMonitor {
error,
vendorCode,
Utils.isDebugEnabled(context, targetUserId),
- latency);
+ sanitizeLatency(latency));
}
protected final void logOnAuthenticated(Context context, boolean authenticated,
@@ -165,7 +170,7 @@ public abstract class LoggableMonitor {
statsClient(),
requireConfirmation,
authState,
- latency,
+ sanitizeLatency(latency),
Utils.isDebugEnabled(context, targetUserId));
}
@@ -183,8 +188,16 @@ public abstract class LoggableMonitor {
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ENROLLED,
statsModality(),
targetUserId,
- latency,
+ sanitizeLatency(latency),
enrollSuccessful);
}
+ private long sanitizeLatency(long latency) {
+ if (latency < 0) {
+ Slog.w(TAG, "found a negative latency : " + latency);
+ return -1;
+ }
+ return latency;
+ }
+
}
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index 2d4ab6308a8b..8f3fd36f411b 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -80,7 +80,7 @@ public class Utils {
* @param authenticators composed of one or more values from {@link Authenticators}
* @return true if device credential is allowed.
*/
- public static boolean isDeviceCredentialAllowed(@Authenticators.Types int authenticators) {
+ public static boolean isCredentialRequested(@Authenticators.Types int authenticators) {
return (authenticators & Authenticators.DEVICE_CREDENTIAL) != 0;
}
@@ -88,8 +88,8 @@ public class Utils {
* @param bundle should be first processed by {@link #combineAuthenticatorBundles(Bundle)}
* @return true if device credential is allowed.
*/
- public static boolean isDeviceCredentialAllowed(Bundle bundle) {
- return isDeviceCredentialAllowed(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
+ public static boolean isCredentialRequested(Bundle bundle) {
+ return isCredentialRequested(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
}
/**
@@ -120,7 +120,7 @@ public class Utils {
* @param bundle should be first processed by {@link #combineAuthenticatorBundles(Bundle)}
* @return true if biometric authentication is allowed.
*/
- public static boolean isBiometricAllowed(Bundle bundle) {
+ public static boolean isBiometricRequested(Bundle bundle) {
return getPublicBiometricStrength(bundle) != 0;
}
@@ -169,7 +169,7 @@ public class Utils {
// should be set.
final int biometricBits = authenticators & Authenticators.BIOMETRIC_MIN_STRENGTH;
if (biometricBits == Authenticators.EMPTY_SET
- && isDeviceCredentialAllowed(authenticators)) {
+ && isCredentialRequested(authenticators)) {
return true;
} else if (biometricBits == Authenticators.BIOMETRIC_STRONG) {
return true;
@@ -209,6 +209,9 @@ public class Utils {
case BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT:
biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE;
break;
+ case BiometricConstants.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED:
+ biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED;
+ break;
default:
Slog.e(BiometricService.TAG, "Unhandled result code: " + biometricConstantsCode);
biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE;
diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
index 57d1867b3aca..0a6198863b00 100644
--- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
@@ -41,7 +41,7 @@ import android.hardware.biometrics.IBiometricNativeHandle;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprintClientCallback;
+import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.IFingerprintClientActiveCallback;
@@ -601,6 +601,11 @@ public class FingerprintService extends BiometricServiceBase {
@Override
public void onAcquired(final long deviceId, final int acquiredInfo, final int vendorCode) {
+ onAcquired_2_2(deviceId, acquiredInfo, vendorCode);
+ }
+
+ @Override
+ public void onAcquired_2_2(long deviceId, int acquiredInfo, int vendorCode) {
mHandler.post(() -> {
FingerprintService.super.handleAcquired(deviceId, acquiredInfo, vendorCode);
});
diff --git a/services/core/java/com/android/server/compat/TEST_MAPPING b/services/core/java/com/android/server/compat/TEST_MAPPING
new file mode 100644
index 000000000000..0c30c790c5dd
--- /dev/null
+++ b/services/core/java/com/android/server/compat/TEST_MAPPING
@@ -0,0 +1,21 @@
+{
+ "presubmit": [
+ // Unit tests
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.compat"
+ }
+ ]
+ },
+ // Tests for the TestRule
+ {
+ "name": "PlatformCompatGating"
+ },
+ // CTS tests
+ {
+ "name": "CtsAppCompatHostTestCases#"
+ }
+ ]
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java
index b0e2e6432c77..6a5e963064bb 100644
--- a/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java
+++ b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java
@@ -16,9 +16,7 @@
package com.android.server.incremental;
-import static android.content.pm.InstallationFile.FILE_TYPE_OBB;
import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
-import static android.content.pm.PackageInstaller.LOCATION_MEDIA_OBB;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -183,10 +181,8 @@ public final class IncrementalManagerShellCommand extends ShellCommand {
session = packageInstaller.openSession(sessionId);
for (int i = 0; i < numFiles; i++) {
InstallationFile file = installationFiles.get(i);
- final int location = file.getFileType() == FILE_TYPE_OBB ? LOCATION_MEDIA_OBB
- : LOCATION_DATA_APP;
- session.addFile(location, file.getName(), file.getSize(), file.getMetadata(),
- null);
+ session.addFile(file.getLocation(), file.getName(), file.getLengthBytes(),
+ file.getMetadata(), file.getSignature());
}
session.commit(localReceiver.getIntentSender());
final Intent result = localReceiver.getResult();
@@ -304,7 +300,8 @@ public final class IncrementalManagerShellCommand extends ShellCommand {
}
final byte[] metadata = String.valueOf(index).getBytes(
StandardCharsets.UTF_8);
- fileList.add(new InstallationFile(name, size, metadata));
+ fileList.add(
+ new InstallationFile(LOCATION_DATA_APP, name, size, metadata, null));
break;
}
default:
diff --git a/services/core/java/com/android/server/integrity/model/ComponentBitSize.java b/services/core/java/com/android/server/integrity/model/ComponentBitSize.java
index c3899638f40f..94e6708c3038 100644
--- a/services/core/java/com/android/server/integrity/model/ComponentBitSize.java
+++ b/services/core/java/com/android/server/integrity/model/ComponentBitSize.java
@@ -29,13 +29,14 @@ public final class ComponentBitSize {
public static final int KEY_BITS = 4;
public static final int OPERATOR_BITS = 3;
public static final int CONNECTOR_BITS = 2;
- public static final int SEPARATOR_BITS = 2;
+ public static final int SEPARATOR_BITS = 3;
public static final int VALUE_SIZE_BITS = 8;
public static final int IS_HASHED_BITS = 1;
public static final int ATOMIC_FORMULA_START = 0;
public static final int COMPOUND_FORMULA_START = 1;
public static final int COMPOUND_FORMULA_END = 2;
+ public static final int INSTALLER_ALLOWED_BY_MANIFEST_START = 3;
public static final int DEFAULT_FORMAT_VERSION = 1;
public static final int SIGNAL_BIT = 1;
diff --git a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
index 4b8efafcb6b0..11e8d91dde12 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
@@ -23,6 +23,7 @@ import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMU
import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS;
import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS;
import static com.android.server.integrity.model.ComponentBitSize.FORMAT_VERSION_BITS;
+import static com.android.server.integrity.model.ComponentBitSize.INSTALLER_ALLOWED_BY_MANIFEST_START;
import static com.android.server.integrity.model.ComponentBitSize.IS_HASHED_BITS;
import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS;
import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS;
@@ -35,6 +36,7 @@ import static com.android.server.integrity.parser.BinaryFileOperations.getString
import android.content.integrity.AtomicFormula;
import android.content.integrity.CompoundFormula;
+import android.content.integrity.InstallerAllowedByManifestFormula;
import android.content.integrity.IntegrityFormula;
import android.content.integrity.Rule;
@@ -140,6 +142,8 @@ public class RuleBinaryParser implements RuleParser {
return parseCompoundFormula(bitInputStream);
case COMPOUND_FORMULA_END:
return null;
+ case INSTALLER_ALLOWED_BY_MANIFEST_START:
+ return new InstallerAllowedByManifestFormula();
default:
throw new IllegalArgumentException(
String.format("Unknown formula separator: %s", separator));
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
index 00e054596cd7..8ba5870aef0f 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
@@ -23,6 +23,7 @@ import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS
import static com.android.server.integrity.model.ComponentBitSize.DEFAULT_FORMAT_VERSION;
import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS;
import static com.android.server.integrity.model.ComponentBitSize.FORMAT_VERSION_BITS;
+import static com.android.server.integrity.model.ComponentBitSize.INSTALLER_ALLOWED_BY_MANIFEST_START;
import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS;
import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS;
import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS;
@@ -36,6 +37,7 @@ import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAG
import android.content.integrity.AtomicFormula;
import android.content.integrity.CompoundFormula;
+import android.content.integrity.InstallerAllowedByManifestFormula;
import android.content.integrity.IntegrityFormula;
import android.content.integrity.IntegrityUtils;
import android.content.integrity.Rule;
@@ -202,6 +204,8 @@ public class RuleBinarySerializer implements RuleSerializer {
serializeAtomicFormula((AtomicFormula) formula, bitOutputStream);
} else if (formula instanceof CompoundFormula) {
serializeCompoundFormula((CompoundFormula) formula, bitOutputStream);
+ } else if (formula instanceof InstallerAllowedByManifestFormula) {
+ bitOutputStream.setNext(SEPARATOR_BITS, INSTALLER_ALLOWED_BY_MANIFEST_START);
} else {
throw new IllegalArgumentException(
String.format("Invalid formula type: %s", formula.getClass()));
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
index 6f7d172aabcc..e7235591fb9b 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
@@ -84,6 +84,7 @@ class RuleIndexingDetailsIdentifier {
return getIndexingDetailsForStringAtomicFormula(
(AtomicFormula.StringAtomicFormula) formula);
case IntegrityFormula.LONG_ATOMIC_FORMULA_TAG:
+ case IntegrityFormula.INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG:
case IntegrityFormula.BOOLEAN_ATOMIC_FORMULA_TAG:
// Package name and app certificate related formulas are string atomic formulas.
return new RuleIndexingDetails(NOT_INDEXED);
diff --git a/services/core/java/com/android/server/location/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/GnssMeasurementsProvider.java
index 55e427f70180..6ba5f079264c 100644
--- a/services/core/java/com/android/server/location/GnssMeasurementsProvider.java
+++ b/services/core/java/com/android/server/location/GnssMeasurementsProvider.java
@@ -18,6 +18,7 @@ package com.android.server.location;
import android.content.Context;
import android.location.GnssMeasurementsEvent;
+import android.location.GnssRequest;
import android.location.IGnssMeasurementsListener;
import android.os.Handler;
import android.os.RemoteException;
@@ -33,14 +34,14 @@ import com.android.internal.annotations.VisibleForTesting;
* @hide
*/
public abstract class GnssMeasurementsProvider
- extends RemoteListenerHelper<IGnssMeasurementsListener> {
- private static final String TAG = "GnssMeasurementsProvider";
+ extends RemoteListenerHelper<GnssRequest, IGnssMeasurementsListener> {
+ private static final String TAG = "GnssMeasProvider";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final GnssMeasurementProviderNative mNative;
- private boolean mIsCollectionStarted;
- private boolean mEnableFullTracking;
+ private boolean mStartedCollection;
+ private boolean mStartedFullTracking;
protected GnssMeasurementsProvider(Context context, Handler handler) {
this(context, handler, new GnssMeasurementProviderNative());
@@ -57,8 +58,8 @@ public abstract class GnssMeasurementsProvider
if (DEBUG) {
Log.d(TAG, "resumeIfStarted");
}
- if (mIsCollectionStarted) {
- mNative.startMeasurementCollection(mEnableFullTracking);
+ if (mStartedCollection) {
+ mNative.startMeasurementCollection(mStartedFullTracking);
}
}
@@ -67,18 +68,35 @@ public abstract class GnssMeasurementsProvider
return mNative.isMeasurementSupported();
}
- @Override
- protected int registerWithService() {
+ private boolean getMergedFullTracking() {
int devOptions = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0);
- int fullTrackingToggled = Settings.Global.getInt(mContext.getContentResolver(),
+ int enableFullTracking = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING, 0);
- boolean enableFullTracking = (devOptions == 1 /* Developer Mode enabled */)
- && (fullTrackingToggled == 1 /* Raw Measurements Full Tracking enabled */);
+ boolean enableFullTrackingBySetting = (devOptions == 1 /* Developer Mode enabled */)
+ && (enableFullTracking == 1 /* Raw Measurements Full Tracking enabled */);
+ if (enableFullTrackingBySetting) {
+ return true;
+ }
+
+ synchronized (mListenerMap) {
+ for (IdentifiedListener identifiedListener : mListenerMap.values()) {
+ GnssRequest request = identifiedListener.getRequest();
+ if (request != null && request.isFullTracking()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ protected int registerWithService() {
+ boolean enableFullTracking = getMergedFullTracking();
boolean result = mNative.startMeasurementCollection(enableFullTracking);
if (result) {
- mIsCollectionStarted = true;
- mEnableFullTracking = enableFullTracking;
+ mStartedCollection = true;
+ mStartedFullTracking = enableFullTracking;
return RemoteListenerHelper.RESULT_SUCCESS;
} else {
return RemoteListenerHelper.RESULT_INTERNAL_ERROR;
@@ -89,7 +107,7 @@ public abstract class GnssMeasurementsProvider
protected void unregisterFromService() {
boolean stopped = mNative.stopMeasurementCollection();
if (stopped) {
- mIsCollectionStarted = false;
+ mStartedCollection = false;
}
}
diff --git a/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java b/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java
index 983d1daa2367..fb901e86f494 100644
--- a/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java
+++ b/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java
@@ -33,7 +33,7 @@ import com.android.internal.annotations.VisibleForTesting;
* @hide
*/
public abstract class GnssNavigationMessageProvider
- extends RemoteListenerHelper<IGnssNavigationMessageListener> {
+ extends RemoteListenerHelper<Void, IGnssNavigationMessageListener> {
private static final String TAG = "GnssNavigationMessageProvider";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
diff --git a/services/core/java/com/android/server/location/GnssStatusListenerHelper.java b/services/core/java/com/android/server/location/GnssStatusListenerHelper.java
index eaf63c87d93f..1d16c03fd6f7 100644
--- a/services/core/java/com/android/server/location/GnssStatusListenerHelper.java
+++ b/services/core/java/com/android/server/location/GnssStatusListenerHelper.java
@@ -24,7 +24,8 @@ import android.util.Log;
/**
* Implementation of a handler for {@link IGnssStatusListener}.
*/
-public abstract class GnssStatusListenerHelper extends RemoteListenerHelper<IGnssStatusListener> {
+public abstract class GnssStatusListenerHelper extends
+ RemoteListenerHelper<Void, IGnssStatusListener> {
private static final String TAG = "GnssStatusListenerHelper";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
diff --git a/services/core/java/com/android/server/location/RemoteListenerHelper.java b/services/core/java/com/android/server/location/RemoteListenerHelper.java
index 015227394ffe..11f068533a6d 100644
--- a/services/core/java/com/android/server/location/RemoteListenerHelper.java
+++ b/services/core/java/com/android/server/location/RemoteListenerHelper.java
@@ -17,6 +17,7 @@
package com.android.server.location;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.content.Context;
import android.os.Handler;
@@ -32,9 +33,10 @@ import java.util.Objects;
/**
* A helper class that handles operations in remote listeners.
*
+ * @param <TRequest> the type of request.
* @param <TListener> the type of GNSS data listener.
*/
-public abstract class RemoteListenerHelper<TListener extends IInterface> {
+public abstract class RemoteListenerHelper<TRequest, TListener extends IInterface> {
protected static final int RESULT_SUCCESS = 0;
protected static final int RESULT_NOT_AVAILABLE = 1;
@@ -47,7 +49,7 @@ public abstract class RemoteListenerHelper<TListener extends IInterface> {
protected final Handler mHandler;
private final String mTag;
- private final Map<IBinder, IdentifiedListener> mListenerMap = new HashMap<>();
+ protected final Map<IBinder, IdentifiedListener> mListenerMap = new HashMap<>();
protected final Context mContext;
protected final AppOpsManager mAppOps;
@@ -75,7 +77,8 @@ public abstract class RemoteListenerHelper<TListener extends IInterface> {
/**
* Adds GNSS data listener {@code listener} with caller identify {@code callerIdentify}.
*/
- public void addListener(@NonNull TListener listener, CallerIdentity callerIdentity) {
+ public void addListener(@Nullable TRequest request, @NonNull TListener listener,
+ CallerIdentity callerIdentity) {
Objects.requireNonNull(listener, "Attempted to register a 'null' listener.");
IBinder binder = listener.asBinder();
synchronized (mListenerMap) {
@@ -84,7 +87,7 @@ public abstract class RemoteListenerHelper<TListener extends IInterface> {
return;
}
- IdentifiedListener identifiedListener = new IdentifiedListener(listener,
+ IdentifiedListener identifiedListener = new IdentifiedListener(request, listener,
callerIdentity);
mListenerMap.put(binder, identifiedListener);
@@ -257,14 +260,22 @@ public abstract class RemoteListenerHelper<TListener extends IInterface> {
return RESULT_SUCCESS;
}
- private class IdentifiedListener {
+ protected class IdentifiedListener {
+ @Nullable private final TRequest mRequest;
private final TListener mListener;
private final CallerIdentity mCallerIdentity;
- private IdentifiedListener(@NonNull TListener listener, CallerIdentity callerIdentity) {
+ private IdentifiedListener(@Nullable TRequest request, @NonNull TListener listener,
+ CallerIdentity callerIdentity) {
mListener = listener;
+ mRequest = request;
mCallerIdentity = callerIdentity;
}
+
+ @Nullable
+ protected TRequest getRequest() {
+ return mRequest;
+ }
}
private class HandlerRunnable implements Runnable {
diff --git a/services/core/java/com/android/server/location/SettingsHelper.java b/services/core/java/com/android/server/location/SettingsHelper.java
index 9163490fa777..6d1d1f901eb3 100644
--- a/services/core/java/com/android/server/location/SettingsHelper.java
+++ b/services/core/java/com/android/server/location/SettingsHelper.java
@@ -135,6 +135,24 @@ public class SettingsHelper {
}
/**
+ * Set location enabled for a user.
+ */
+ public void setLocationEnabled(boolean enabled, int userId) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ Settings.Secure.putIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCATION_MODE,
+ enabled
+ ? Settings.Secure.LOCATION_MODE_ON
+ : Settings.Secure.LOCATION_MODE_OFF,
+ userId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
* Add a listener for changes to the location enabled setting. Callbacks occur on an unspecified
* thread.
*/
diff --git a/services/core/java/com/android/server/location/TEST_MAPPING b/services/core/java/com/android/server/location/TEST_MAPPING
index 2e21fa67a967..214d2f3f5646 100644
--- a/services/core/java/com/android/server/location/TEST_MAPPING
+++ b/services/core/java/com/android/server/location/TEST_MAPPING
@@ -1,10 +1,19 @@
{
"presubmit": [
{
+ "name": "CtsLocationFineTestCases"
+ },
+ {
"name": "CtsLocationCoarseTestCases"
},
{
"name": "CtsLocationNoneTestCases"
+ },
+ {
+ "name": "FrameworksMockingServicesTests",
+ "options": [{
+ "include-filter": "com.android.server.location"
+ }]
}
]
} \ No newline at end of file
diff --git a/services/core/java/com/android/server/location/gnss/GnssManagerService.java b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
index 2bab9fa67eb0..02c1bddd8c00 100644
--- a/services/core/java/com/android/server/location/gnss/GnssManagerService.java
+++ b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
@@ -25,6 +25,7 @@ import android.app.AppOpsManager;
import android.content.Context;
import android.location.GnssCapabilities;
import android.location.GnssMeasurementCorrections;
+import android.location.GnssRequest;
import android.location.IBatchedLocationCallback;
import android.location.IGnssMeasurementsListener;
import android.location.IGnssNavigationMessageListener;
@@ -96,15 +97,15 @@ public class GnssManagerService {
private final IGpsGeofenceHardware mGpsGeofenceProxy;
@GuardedBy("mGnssMeasurementsListeners")
- private final ArrayMap<IBinder, LinkedListener<IGnssMeasurementsListener>>
+ private final ArrayMap<IBinder, LinkedListener<GnssRequest, IGnssMeasurementsListener>>
mGnssMeasurementsListeners = new ArrayMap<>();
@GuardedBy("mGnssNavigationMessageListeners")
- private final ArrayMap<IBinder, LinkedListener<IGnssNavigationMessageListener>>
+ private final ArrayMap<IBinder, LinkedListener<Void, IGnssNavigationMessageListener>>
mGnssNavigationMessageListeners = new ArrayMap<>();
@GuardedBy("mGnssStatusListeners")
- private final ArrayMap<IBinder, LinkedListener<IGnssStatusListener>>
+ private final ArrayMap<IBinder, LinkedListener<Void, IGnssStatusListener>>
mGnssStatusListeners = new ArrayMap<>();
@GuardedBy("this")
@@ -118,7 +119,8 @@ public class GnssManagerService {
@Nullable private IBatchedLocationCallback mGnssBatchingCallback;
@GuardedBy("mGnssBatchingLock")
- @Nullable private LinkedListener<IBatchedLocationCallback> mGnssBatchingDeathCallback;
+ @Nullable
+ private LinkedListener<Void, IBatchedLocationCallback> mGnssBatchingDeathCallback;
@GuardedBy("mGnssBatchingLock")
private boolean mGnssBatchingInProgress = false;
@@ -272,6 +274,7 @@ public class GnssManagerService {
mGnssBatchingCallback = callback;
mGnssBatchingDeathCallback =
new LinkedListener<>(
+ /* request= */ null,
callback,
"BatchedLocationCallback",
callerIdentity,
@@ -356,36 +359,39 @@ public class GnssManagerService {
}
}
- private <TListener extends IInterface> void updateListenersOnForegroundChangedLocked(
- Map<IBinder, ? extends LinkedListenerBase> gnssDataListeners,
- RemoteListenerHelper<TListener> gnssDataProvider,
+ private <TRequest, TListener extends IInterface> void updateListenersOnForegroundChangedLocked(
+ Map<IBinder, LinkedListener<TRequest, TListener>> gnssDataListeners,
+ RemoteListenerHelper<TRequest, TListener> gnssDataProvider,
Function<IBinder, TListener> mapBinderToListener,
int uid,
boolean foreground) {
- for (Map.Entry<IBinder, ? extends LinkedListenerBase> entry :
+ for (Map.Entry<IBinder, LinkedListener<TRequest, TListener>> entry :
gnssDataListeners.entrySet()) {
- LinkedListenerBase linkedListener = entry.getValue();
+ LinkedListener<TRequest, TListener> linkedListener = entry.getValue();
CallerIdentity callerIdentity = linkedListener.getCallerIdentity();
+ TRequest request = linkedListener.getRequest();
if (callerIdentity.mUid != uid) {
continue;
}
TListener listener = mapBinderToListener.apply(entry.getKey());
if (foreground || isThrottlingExempt(callerIdentity)) {
- gnssDataProvider.addListener(listener, callerIdentity);
+ gnssDataProvider.addListener(request, listener, callerIdentity);
} else {
gnssDataProvider.removeListener(listener);
}
}
}
- private <TListener extends IInterface> boolean addGnssDataListenerLocked(
+ private <TListener extends IInterface, TRequest> boolean addGnssDataListenerLocked(
+ @Nullable TRequest request,
TListener listener,
String packageName,
@Nullable String featureId,
@NonNull String listenerIdentifier,
- RemoteListenerHelper<TListener> gnssDataProvider,
- ArrayMap<IBinder, LinkedListener<TListener>> gnssDataListeners,
+ RemoteListenerHelper<TRequest, TListener> gnssDataProvider,
+ ArrayMap<IBinder,
+ LinkedListener<TRequest, TListener>> gnssDataListeners,
Consumer<TListener> binderDeathCallback) {
mContext.enforceCallingPermission(Manifest.permission.ACCESS_FINE_LOCATION, null);
@@ -395,7 +401,7 @@ public class GnssManagerService {
CallerIdentity callerIdentity = new CallerIdentity(Binder.getCallingUid(),
Binder.getCallingPid(), packageName, featureId, listenerIdentifier);
- LinkedListener<TListener> linkedListener = new LinkedListener<>(listener,
+ LinkedListener<TRequest, TListener> linkedListener = new LinkedListener<>(request, listener,
listenerIdentifier, callerIdentity, binderDeathCallback);
IBinder binder = listener.asBinder();
if (!linkedListener.linkToListenerDeathNotificationLocked(binder)) {
@@ -419,15 +425,15 @@ public class GnssManagerService {
}
if (mAppForegroundHelper.isAppForeground(callerIdentity.mUid)
|| isThrottlingExempt(callerIdentity)) {
- gnssDataProvider.addListener(listener, callerIdentity);
+ gnssDataProvider.addListener(request, listener, callerIdentity);
}
return true;
}
- private <TListener extends IInterface> void removeGnssDataListenerLocked(
+ private <TRequest, TListener extends IInterface> void removeGnssDataListenerLocked(
TListener listener,
- RemoteListenerHelper<TListener> gnssDataProvider,
- ArrayMap<IBinder, LinkedListener<TListener>> gnssDataListeners) {
+ RemoteListenerHelper<TRequest, TListener> gnssDataProvider,
+ ArrayMap<IBinder, LinkedListener<TRequest, TListener>> gnssDataListeners) {
if (gnssDataProvider == null) {
Log.e(
TAG,
@@ -437,7 +443,7 @@ public class GnssManagerService {
}
IBinder binder = listener.asBinder();
- LinkedListener<TListener> linkedListener =
+ LinkedListener<TRequest, TListener> linkedListener =
gnssDataListeners.remove(binder);
if (linkedListener == null) {
return;
@@ -467,6 +473,7 @@ public class GnssManagerService {
@Nullable String featureId) {
synchronized (mGnssStatusListeners) {
return addGnssDataListenerLocked(
+ /* request= */ null,
listener,
packageName,
featureId,
@@ -489,11 +496,17 @@ public class GnssManagerService {
/**
* Adds a GNSS measurements listener.
*/
- public boolean addGnssMeasurementsListener(
- IGnssMeasurementsListener listener, String packageName, @Nullable String featureId,
+ public boolean addGnssMeasurementsListener(@Nullable GnssRequest request,
+ IGnssMeasurementsListener listener, String packageName,
+ @Nullable String featureId,
@NonNull String listenerIdentifier) {
+ if (request != null && request.isFullTracking()) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.LOCATION_HARDWARE,
+ null);
+ }
synchronized (mGnssMeasurementsListeners) {
return addGnssDataListenerLocked(
+ request,
listener,
packageName,
featureId,
@@ -538,6 +551,7 @@ public class GnssManagerService {
@Nullable String featureId, @NonNull String listenerIdentifier) {
synchronized (mGnssNavigationMessageListeners) {
return addGnssDataListenerLocked(
+ /* request= */ null,
listener,
packageName,
featureId,
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 0cd1c259dd9e..e9f84fd419d6 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
+import android.Manifest.permission;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
@@ -305,6 +306,10 @@ public class LauncherAppsService extends SystemService {
final int callingUserId = injectCallingUserId();
if (targetUserId == callingUserId) return true;
+ if (mContext.checkCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS_FULL)
+ == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
long ident = injectClearCallingIdentity();
try {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 944280d6db88..97defcdd3bc7 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -214,7 +214,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill";
private static final int[] EMPTY_CHILD_SESSION_ARRAY = EmptyArray.INT;
- private static final FileInfo[] EMPTY_FILE_INFO_ARRAY = {};
+ private static final InstallationFile[] EMPTY_INSTALLATION_FILE_ARRAY = {};
private static final String SYSTEM_DATA_LOADER_PACKAGE = "android";
@@ -309,33 +309,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@GuardedBy("mLock")
private int mParentSessionId;
- static class FileInfo {
- public final int location;
- public final String name;
- public final Long lengthBytes;
- public final byte[] metadata;
- public final byte[] signature;
-
- public static FileInfo added(int location, String name, Long lengthBytes, byte[] metadata,
- byte[] signature) {
- return new FileInfo(location, name, lengthBytes, metadata, signature);
- }
-
- public static FileInfo removed(int location, String name) {
- return new FileInfo(location, name, -1L, null, null);
- }
-
- FileInfo(int location, String name, Long lengthBytes, byte[] metadata, byte[] signature) {
- this.location = location;
- this.name = name;
- this.lengthBytes = lengthBytes;
- this.metadata = metadata;
- this.signature = signature;
- }
- }
-
@GuardedBy("mLock")
- private ArrayList<FileInfo> mFiles = new ArrayList<>();
+ private ArrayList<InstallationFile> mFiles = new ArrayList<>();
@GuardedBy("mLock")
private boolean mStagedSessionApplied;
@@ -508,7 +483,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
PackageSessionProvider sessionProvider, Looper looper, StagingManager stagingManager,
int sessionId, int userId, int installerUid, @NonNull InstallSource installSource,
SessionParams params, long createdMillis,
- File stageDir, String stageCid, FileInfo[] files, boolean prepared,
+ File stageDir, String stageCid, InstallationFile[] files, boolean prepared,
boolean committed, boolean sealed,
@Nullable int[] childSessionIds, int parentSessionId, boolean isReady,
boolean isFailed, boolean isApplied, int stagedSessionErrorCode,
@@ -539,7 +514,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
this.mParentSessionId = parentSessionId;
if (files != null) {
- for (FileInfo file : files) {
+ for (InstallationFile file : files) {
mFiles.add(file);
}
}
@@ -738,7 +713,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
String[] result = new String[mFiles.size()];
for (int i = 0, size = mFiles.size(); i < size; ++i) {
- result[i] = mFiles.get(i).name;
+ result[i] = mFiles.get(i).getName();
}
return result;
}
@@ -2425,7 +2400,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
assertCallerIsOwnerOrRootLocked();
assertPreparedAndNotSealedLocked("addFile");
- mFiles.add(FileInfo.added(location, name, lengthBytes, metadata, signature));
+ mFiles.add(new InstallationFile(location, name, lengthBytes, metadata, signature));
}
}
@@ -2443,7 +2418,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
assertCallerIsOwnerOrRootLocked();
assertPreparedAndNotSealedLocked("removeFile");
- mFiles.add(FileInfo.removed(location, getRemoveMarkerName(name)));
+ mFiles.add(new InstallationFile(location, getRemoveMarkerName(name), -1, null, null));
}
}
@@ -2461,17 +2436,16 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
final List<InstallationFile> addedFiles = new ArrayList<>(mFiles.size());
- for (FileInfo file : mFiles) {
- if (sAddedFilter.accept(new File(this.stageDir, file.name))) {
- addedFiles.add(new InstallationFile(
- file.name, file.lengthBytes, file.metadata));
+ for (InstallationFile file : mFiles) {
+ if (sAddedFilter.accept(new File(this.stageDir, file.getName()))) {
+ addedFiles.add(file);
}
}
final List<String> removedFiles = new ArrayList<>(mFiles.size());
- for (FileInfo file : mFiles) {
- if (sRemovedFilter.accept(new File(this.stageDir, file.name))) {
- String name = file.name.substring(
- 0, file.name.length() - REMOVE_MARKER_EXTENSION.length());
+ for (InstallationFile file : mFiles) {
+ if (sRemovedFilter.accept(new File(this.stageDir, file.getName()))) {
+ String name = file.getName().substring(
+ 0, file.getName().length() - REMOVE_MARKER_EXTENSION.length());
removedFiles.add(name);
}
}
@@ -2970,13 +2944,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
writeIntAttribute(out, ATTR_SESSION_ID, childSessionId);
out.endTag(null, TAG_CHILD_SESSION);
}
- for (FileInfo fileInfo : mFiles) {
+ for (InstallationFile file : mFiles) {
out.startTag(null, TAG_SESSION_FILE);
- writeIntAttribute(out, ATTR_LOCATION, fileInfo.location);
- writeStringAttribute(out, ATTR_NAME, fileInfo.name);
- writeLongAttribute(out, ATTR_LENGTH_BYTES, fileInfo.lengthBytes);
- writeByteArrayAttribute(out, ATTR_METADATA, fileInfo.metadata);
- writeByteArrayAttribute(out, ATTR_SIGNATURE, fileInfo.signature);
+ writeIntAttribute(out, ATTR_LOCATION, file.getLocation());
+ writeStringAttribute(out, ATTR_NAME, file.getName());
+ writeLongAttribute(out, ATTR_LENGTH_BYTES, file.getLengthBytes());
+ writeByteArrayAttribute(out, ATTR_METADATA, file.getMetadata());
+ writeByteArrayAttribute(out, ATTR_SIGNATURE, file.getSignature());
out.endTag(null, TAG_SESSION_FILE);
}
}
@@ -3088,7 +3062,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
List<String> grantedRuntimePermissions = new ArrayList<>();
List<String> whitelistedRestrictedPermissions = new ArrayList<>();
List<Integer> childSessionIds = new ArrayList<>();
- List<FileInfo> files = new ArrayList<>();
+ List<InstallationFile> files = new ArrayList<>();
int outerDepth = in.getDepth();
int type;
while ((type = in.next()) != XmlPullParser.END_DOCUMENT
@@ -3107,7 +3081,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
childSessionIds.add(readIntAttribute(in, ATTR_SESSION_ID, SessionInfo.INVALID_ID));
}
if (TAG_SESSION_FILE.equals(in.getName())) {
- files.add(new FileInfo(
+ files.add(new InstallationFile(
readIntAttribute(in, ATTR_LOCATION, 0),
readStringAttribute(in, ATTR_NAME),
readLongAttribute(in, ATTR_LENGTH_BYTES, -1),
@@ -3135,16 +3109,16 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
childSessionIdsArray = EMPTY_CHILD_SESSION_ARRAY;
}
- FileInfo[] fileInfosArray = null;
+ InstallationFile[] fileArray = null;
if (!files.isEmpty()) {
- fileInfosArray = files.toArray(EMPTY_FILE_INFO_ARRAY);
+ fileArray = files.toArray(EMPTY_INSTALLATION_FILE_ARRAY);
}
InstallSource installSource = InstallSource.create(installInitiatingPackageName,
installOriginatingPackageName, installerPackageName);
return new PackageInstallerSession(callback, context, pm, sessionProvider,
installerThread, stagingManager, sessionId, userId, installerUid,
- installSource, params, createdMillis, stageDir, stageCid, fileInfosArray,
+ installSource, params, createdMillis, stageDir, stageCid, fileArray,
prepared, committed, sealed, childSessionIdsArray, parentSessionId,
isReady, isFailed, isApplied, stagedSessionErrorCode, stagedSessionErrorMessage);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index cb9404397f3d..c69a62d406aa 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -1163,7 +1163,7 @@ class PackageManagerShellCommand extends ShellCommand {
final InstallParams params = makeInstallParams();
if (params.sessionParams.dataLoaderParams == null) {
params.sessionParams.setDataLoaderParams(
- PackageManagerShellCommandDataLoader.getDataLoaderParams(this));
+ PackageManagerShellCommandDataLoader.getStreamingDataLoaderParams(this));
}
return doRunInstall(params);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
index 5dca9e147e31..4170be4a913d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
@@ -54,7 +54,7 @@ public class PackageManagerShellCommandDataLoader extends DataLoaderService {
private static final String STDIN_PATH = "-";
- static DataLoaderParams getDataLoaderParams(ShellCommand shellCommand) {
+ private static String getDataLoaderParamsArgs(ShellCommand shellCommand) {
int commandId;
synchronized (sShellCommands) {
// Clean up old references.
@@ -78,8 +78,12 @@ public class PackageManagerShellCommandDataLoader extends DataLoaderService {
sShellCommands.put(commandId, new WeakReference<>(shellCommand));
}
- final String args = SHELL_COMMAND_ID_PREFIX + commandId;
- return DataLoaderParams.forStreaming(new ComponentName(PACKAGE, CLASS), args);
+ return SHELL_COMMAND_ID_PREFIX + commandId;
+ }
+
+ static DataLoaderParams getStreamingDataLoaderParams(ShellCommand shellCommand) {
+ return DataLoaderParams.forStreaming(new ComponentName(PACKAGE, CLASS),
+ getDataLoaderParamsArgs(shellCommand));
}
private static int extractShellCommandId(String args) {
@@ -133,17 +137,17 @@ public class PackageManagerShellCommandDataLoader extends DataLoaderService {
return false;
}
try {
- for (InstallationFile fileInfo : addedFiles) {
- String filePath = new String(fileInfo.getMetadata(), StandardCharsets.UTF_8);
+ for (InstallationFile file : addedFiles) {
+ String filePath = new String(file.getMetadata(), StandardCharsets.UTF_8);
if (STDIN_PATH.equals(filePath) || TextUtils.isEmpty(filePath)) {
final ParcelFileDescriptor inFd = ParcelFileDescriptor.dup(
shellCommand.getInFileDescriptor());
- mConnector.writeData(fileInfo.getName(), 0, fileInfo.getSize(), inFd);
+ mConnector.writeData(file.getName(), 0, file.getLengthBytes(), inFd);
} else {
ParcelFileDescriptor incomingFd = null;
try {
incomingFd = shellCommand.openFileForSystem(filePath, "r");
- mConnector.writeData(fileInfo.getName(), 0, incomingFd.getStatSize(),
+ mConnector.writeData(file.getName(), 0, incomingFd.getStatSize(),
incomingFd);
} finally {
IoUtils.closeQuietly(incomingFd);
@@ -159,7 +163,8 @@ public class PackageManagerShellCommandDataLoader extends DataLoaderService {
}
@Override
- public DataLoaderService.DataLoader onCreateDataLoader() {
+ public DataLoaderService.DataLoader onCreateDataLoader(
+ @NonNull DataLoaderParams dataLoaderParams) {
return new DataLoader();
}
}
diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
index 77bb48eadc41..4c40448085ff 100644
--- a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
+++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
@@ -89,7 +89,7 @@ class UserSystemPackageInstaller {
* <ul>
* <li> 0 - disable whitelist (install all system packages; no logging)</li>
* <li> 1 - enforce (only install system packages if they are whitelisted)</li>
- * <li> 2 - log (log when a non-whitelisted package is run)</li>
+ * <li> 2 - log (log non-whitelisted packages)</li>
* <li> 4 - for all users: implicitly whitelist any package not mentioned in the whitelist</li>
* <li> 8 - for SYSTEM: implicitly whitelist any package not mentioned in the whitelist</li>
* <li> 16 - ignore OTAs (don't install system packages during OTAs)</li>
diff --git a/services/core/java/com/android/server/power/PreRebootLogger.java b/services/core/java/com/android/server/power/PreRebootLogger.java
new file mode 100644
index 000000000000..cda00b404c0b
--- /dev/null
+++ b/services/core/java/com/android/server/power/PreRebootLogger.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.Environment;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+
+/**
+ * Provides utils to dump/wipe pre-reboot information.
+ */
+final class PreRebootLogger {
+ private static final String TAG = "PreRebootLogger";
+ private static final String PREREBOOT_DIR = "prereboot";
+
+ private static final String[] BUFFERS_TO_DUMP = {"system"};
+ private static final String[] SERVICES_TO_DUMP = {Context.ROLLBACK_SERVICE, "package"};
+
+ private static final Object sLock = new Object();
+
+ /**
+ * Process pre-reboot information. Dump pre-reboot information to {@link #PREREBOOT_DIR} if
+ * enabled {@link Settings.Global#ADB_ENABLED}; wipe dumped information otherwise.
+ */
+ static void log(Context context) {
+ log(context, getDumpDir());
+ }
+
+ @VisibleForTesting
+ static void log(Context context, @NonNull File dumpDir) {
+ if (Settings.Global.getInt(
+ context.getContentResolver(), Settings.Global.ADB_ENABLED, 0) == 1) {
+ Slog.d(TAG, "Dumping pre-reboot information...");
+ dump(dumpDir);
+ } else {
+ Slog.d(TAG, "Wiping pre-reboot information...");
+ wipe(dumpDir);
+ }
+ }
+
+ private static void dump(@NonNull File dumpDir) {
+ synchronized (sLock) {
+ for (String buffer : BUFFERS_TO_DUMP) {
+ dumpLogsLocked(dumpDir, buffer);
+ }
+ for (String service : SERVICES_TO_DUMP) {
+ dumpServiceLocked(dumpDir, service);
+ }
+ }
+ }
+
+ private static void wipe(@NonNull File dumpDir) {
+ synchronized (sLock) {
+ for (File file : dumpDir.listFiles()) {
+ file.delete();
+ }
+ }
+ }
+
+ private static File getDumpDir() {
+ final File dumpDir = new File(Environment.getDataMiscDirectory(), PREREBOOT_DIR);
+ if (!dumpDir.exists() || !dumpDir.isDirectory()) {
+ throw new UnsupportedOperationException("Pre-reboot dump directory not found");
+ }
+ return dumpDir;
+ }
+
+ @GuardedBy("sLock")
+ private static void dumpLogsLocked(@NonNull File dumpDir, @NonNull String buffer) {
+ try {
+ final File dumpFile = new File(dumpDir, buffer);
+ if (dumpFile.createNewFile()) {
+ dumpFile.setWritable(true /* writable */, true /* ownerOnly */);
+ } else {
+ // Wipes dumped information in existing file before recording new information.
+ new FileWriter(dumpFile, false).flush();
+ }
+
+ final String[] cmdline =
+ {"logcat", "-d", "-b", buffer, "-f", dumpFile.getAbsolutePath()};
+ Runtime.getRuntime().exec(cmdline).waitFor();
+ } catch (IOException | InterruptedException e) {
+ Slog.d(TAG, "Dump system log buffer before reboot fail", e);
+ }
+ }
+
+ @GuardedBy("sLock")
+ private static void dumpServiceLocked(@NonNull File dumpDir, @NonNull String serviceName) {
+ final IBinder binder = ServiceManager.checkService(serviceName);
+ if (binder == null) {
+ return;
+ }
+
+ try {
+ final File dumpFile = new File(dumpDir, serviceName);
+ final ParcelFileDescriptor fd = ParcelFileDescriptor.open(dumpFile,
+ ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_TRUNCATE
+ | ParcelFileDescriptor.MODE_WRITE_ONLY);
+ binder.dump(fd.getFileDescriptor(), ArrayUtils.emptyArray(String.class));
+ } catch (FileNotFoundException | RemoteException e) {
+ Slog.d(TAG, String.format("Dump %s service before reboot fail", serviceName), e);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index cc1cddd3a111..bc722f181650 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -44,6 +44,7 @@ import android.os.Vibrator;
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.Slog;
import android.util.TimingsTraceLog;
import android.view.WindowManager;
@@ -446,6 +447,15 @@ public final class ShutdownThread extends Thread {
SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1");
}
+ shutdownTimingLog.traceBegin("DumpPreRebootInfo");
+ try {
+ Slog.i(TAG, "Logging pre-reboot information...");
+ PreRebootLogger.log(mContext);
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to log pre-reboot information", e);
+ }
+ shutdownTimingLog.traceEnd(); // DumpPreRebootInfo
+
metricStarted(METRIC_SEND_BROADCAST);
shutdownTimingLog.traceBegin("SendShutdownBroadcast");
Log.i(TAG, "Sending shutdown broadcast...");
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index 5abd9f0698d3..7b96777b5987 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -181,15 +181,6 @@ class Rollback {
private int mNumPackageSessionsWithSuccess;
/**
- * A temp flag to facilitate merging of the 2 rollback collections managed by
- * RollbackManagerServiceImpl. True if this rollback is in the process of enabling and was
- * originally managed by RollbackManagerServiceImpl#mNewRollbacks.
- * TODO: remove this flag when merge is completed.
- */
- @GuardedBy("mLock")
- private boolean mIsNewRollback = false;
-
- /**
* Constructs a new, empty Rollback instance.
*
* @param rollbackId the id of the rollback.
@@ -837,18 +828,6 @@ class Rollback {
}
}
- void setIsNewRollback(boolean newRollback) {
- synchronized (mLock) {
- mIsNewRollback = newRollback;
- }
- }
-
- boolean isNewRollback() {
- synchronized (mLock) {
- return mIsNewRollback;
- }
- }
-
static String rollbackStateToString(@RollbackState int state) {
switch (state) {
case Rollback.ROLLBACK_STATE_ENABLING: return "enabling";
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 1421258c12f6..91e7cc981b89 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -164,8 +164,16 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
// Load rollback data from device storage.
synchronized (mLock) {
mRollbacks = mRollbackStore.loadRollbacks();
- for (Rollback rollback : mRollbacks) {
- mAllocatedRollbackIds.put(rollback.info.getRollbackId(), true);
+ if (!context.getPackageManager().isDeviceUpgrading()) {
+ for (Rollback rollback : mRollbacks) {
+ mAllocatedRollbackIds.put(rollback.info.getRollbackId(), true);
+ }
+ } else {
+ // Delete rollbacks when build fingerprint has changed.
+ for (Rollback rollback : mRollbacks) {
+ rollback.delete(mAppDataRollbackHelper);
+ }
+ mRollbacks.clear();
}
}
@@ -788,14 +796,13 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
Rollback newRollback;
synchronized (mLock) {
- // See if we already have a NewRollback that contains this package
- // session. If not, create a NewRollback for the parent session
+ // See if we already have a Rollback that contains this package
+ // session. If not, create a new Rollback for the parent session
// that we will use for all the packages in the session.
- newRollback = getNewRollbackForPackageSessionLocked(packageSession.getSessionId());
+ newRollback = getRollbackForSessionLocked(packageSession.getSessionId());
if (newRollback == null) {
newRollback = createNewRollbackLocked(parentSession);
mRollbacks.add(newRollback);
- newRollback.setIsNewRollback(true);
}
}
newRollback.addToken(token);
@@ -1148,24 +1155,23 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
}
if (success) {
- Rollback newRollback;
+ Rollback rollback;
synchronized (mLock) {
- newRollback = getNewRollbackForPackageSessionLocked(sessionId);
- if (newRollback != null && newRollback.notifySessionWithSuccess()) {
- mRollbacks.remove(newRollback);
- newRollback.setIsNewRollback(false);
- } else {
- // Not all child sessions finished with success.
- // Don't enable the rollback yet.
- newRollback = null;
+ rollback = getRollbackForSessionLocked(sessionId);
+ if (rollback == null || rollback.isStaged() || !rollback.isEnabling()
+ || !rollback.notifySessionWithSuccess()) {
+ return;
}
+ // All child sessions finished with success. We can enable this rollback now.
+ // TODO: refactor #completeEnableRollback so we won't remove 'rollback' from
+ // mRollbacks here and add it back in #completeEnableRollback later.
+ mRollbacks.remove(rollback);
}
-
- if (newRollback != null) {
- Rollback rollback = completeEnableRollback(newRollback);
- if (rollback != null && !rollback.isStaged()) {
- makeRollbackAvailable(rollback);
- }
+ // TODO: Now #completeEnableRollback returns the same rollback object as the
+ // parameter on success. It would be more readable to return a boolean to indicate
+ // success or failure.
+ if (completeEnableRollback(rollback) != null) {
+ makeRollbackAvailable(rollback);
}
} else {
synchronized (mLock) {
@@ -1354,22 +1360,4 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
}
return null;
}
-
- /**
- * Returns the NewRollback associated with the given package session.
- * Returns null if no NewRollback is found for the given package
- * session.
- */
- @WorkerThread
- @GuardedBy("mLock")
- Rollback getNewRollbackForPackageSessionLocked(int packageSessionId) {
- // We expect mRollbacks to be a very small list; linear search
- // should be plenty fast.
- for (Rollback rollback: mRollbacks) {
- if (rollback.isNewRollback() && rollback.containsSessionId(packageSessionId)) {
- return rollback;
- }
- }
- return null;
- }
}
diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
index 34d2c16ed0ac..58455ca2753b 100644
--- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
+++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
@@ -36,7 +36,7 @@ import android.service.textclassifier.ITextClassifierService;
import android.service.textclassifier.TextClassifierService;
import android.service.textclassifier.TextClassifierService.ConnectionState;
import android.text.TextUtils;
-import android.util.LruCache;
+import android.util.ArrayMap;
import android.util.Slog;
import android.util.SparseArray;
import android.view.textclassifier.ConversationActions;
@@ -65,6 +65,7 @@ import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Queue;
@@ -146,11 +147,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi
private final Object mLock;
@GuardedBy("mLock")
final SparseArray<UserState> mUserStates = new SparseArray<>();
- // SystemTextClassifier.onDestroy() is not guaranteed to be called, use LruCache here
- // to avoid leak.
- @GuardedBy("mLock")
- private final LruCache<TextClassificationSessionId, TextClassificationContext>
- mSessionContextCache = new LruCache<>(40);
+ private final SessionCache mSessionCache;
private final TextClassificationConstants mSettings;
@Nullable
private final String mDefaultTextClassifierPackage;
@@ -165,6 +162,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi
PackageManager packageManager = mContext.getPackageManager();
mDefaultTextClassifierPackage = packageManager.getDefaultTextClassifierPackageName();
mSystemTextClassifierPackage = packageManager.getSystemTextClassifierPackageName();
+ mSessionCache = new SessionCache(mLock);
}
private void startListenSettings() {
@@ -314,7 +312,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi
classificationContext.getUseDefaultTextClassifier(),
service -> {
service.onCreateTextClassificationSession(classificationContext, sessionId);
- mSessionContextCache.put(sessionId, classificationContext);
+ mSessionCache.put(sessionId, classificationContext);
},
"onCreateTextClassificationSession",
NO_OP_CALLBACK);
@@ -326,14 +324,14 @@ public final class TextClassificationManagerService extends ITextClassifierServi
Objects.requireNonNull(sessionId);
synchronized (mLock) {
- TextClassificationContext textClassificationContext =
- mSessionContextCache.get(sessionId);
+ final StrippedTextClassificationContext textClassificationContext =
+ mSessionCache.get(sessionId);
final int userId = textClassificationContext != null
- ? textClassificationContext.getUserId()
+ ? textClassificationContext.userId
: UserHandle.getCallingUserId();
final boolean useDefaultTextClassifier =
textClassificationContext != null
- ? textClassificationContext.getUseDefaultTextClassifier()
+ ? textClassificationContext.useDefaultTextClassifier
: true;
handleRequest(
userId,
@@ -342,7 +340,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi
useDefaultTextClassifier,
service -> {
service.onDestroyTextClassificationSession(sessionId);
- mSessionContextCache.remove(sessionId);
+ mSessionCache.remove(sessionId);
},
"onDestroyTextClassificationSession",
NO_OP_CALLBACK);
@@ -409,7 +407,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi
pw.decreaseIndent();
}
}
- pw.println("Number of active sessions: " + mSessionContextCache.size());
+ pw.println("Number of active sessions: " + mSessionCache.size());
}
}
@@ -568,6 +566,81 @@ public final class TextClassificationManagerService extends ITextClassifierServi
}
}
+ /**
+ * Stores the stripped down version of {@link TextClassificationContext}s, i.e. {@link
+ * StrippedTextClassificationContext}, keyed by {@link TextClassificationSessionId}. Sessions
+ * are cleaned up automatically when the client process is dead.
+ */
+ static final class SessionCache {
+ @NonNull
+ private final Object mLock;
+ @NonNull
+ @GuardedBy("mLock")
+ private final Map<TextClassificationSessionId, StrippedTextClassificationContext> mCache =
+ new ArrayMap<>();
+ @NonNull
+ @GuardedBy("mLock")
+ private final Map<TextClassificationSessionId, DeathRecipient> mDeathRecipients =
+ new ArrayMap<>();
+
+ SessionCache(@NonNull Object lock) {
+ mLock = Objects.requireNonNull(lock);
+ }
+
+ void put(@NonNull TextClassificationSessionId sessionId,
+ @NonNull TextClassificationContext textClassificationContext) {
+ synchronized (mLock) {
+ mCache.put(sessionId,
+ new StrippedTextClassificationContext(textClassificationContext));
+ try {
+ DeathRecipient deathRecipient = () -> remove(sessionId);
+ sessionId.getToken().linkToDeath(deathRecipient, /* flags= */ 0);
+ mDeathRecipients.put(sessionId, deathRecipient);
+ } catch (RemoteException e) {
+ Slog.w(LOG_TAG, "SessionCache: Failed to link to death", e);
+ }
+ }
+ }
+
+ @Nullable
+ StrippedTextClassificationContext get(@NonNull TextClassificationSessionId sessionId) {
+ Objects.requireNonNull(sessionId);
+ synchronized (mLock) {
+ return mCache.get(sessionId);
+ }
+ }
+
+ void remove(@NonNull TextClassificationSessionId sessionId) {
+ Objects.requireNonNull(sessionId);
+ synchronized (mLock) {
+ DeathRecipient deathRecipient = mDeathRecipients.get(sessionId);
+ if (deathRecipient != null) {
+ sessionId.getToken().unlinkToDeath(deathRecipient, /* flags= */ 0);
+ }
+ mDeathRecipients.remove(sessionId);
+ mCache.remove(sessionId);
+ }
+ }
+
+ int size() {
+ synchronized (mLock) {
+ return mCache.size();
+ }
+ }
+ }
+
+ /** A stripped down version of {@link TextClassificationContext}. */
+ static class StrippedTextClassificationContext {
+ @UserIdInt
+ public final int userId;
+ public final boolean useDefaultTextClassifier;
+
+ StrippedTextClassificationContext(TextClassificationContext textClassificationContext) {
+ userId = textClassificationContext.getUserId();
+ useDefaultTextClassifier = textClassificationContext.getUseDefaultTextClassifier();
+ }
+ }
+
private final class UserState {
@UserIdInt
final int mUserId;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index a54f5d43751c..aa6d8545f705 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -7674,7 +7674,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mainWindow.getContentInsets(insets);
InsetUtils.addInsets(insets, getLetterboxInsets());
return new RemoteAnimationTarget(task.mTaskId, record.getMode(),
- record.mAdapter.mCapturedLeash, !task.fillsParent(),
+ record.mAdapter.mCapturedLeash, !fillsParent(),
mainWindow.mWinAnimator.mLastClipRect, insets,
getPrefixOrderIndex(), record.mAdapter.mPosition,
record.mAdapter.mStackBounds, task.getWindowConfiguration(),
@@ -7692,4 +7692,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
win.getAnimationFrames(outFrame, outInsets, outStableInsets, outSurfaceInsets);
}
+
+ void setPictureInPictureParams(PictureInPictureParams p) {
+ pictureInPictureArgs.copyOnlySet(p);
+ getTask().getRootTask().setPictureInPictureParams(p);
+ }
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 5f3e3a39490c..4b96ea0a85dd 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2795,6 +2795,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
}
+ // TODO(148895075): deprecate and replace with task equivalents
@Override
public List<ActivityManager.StackInfo> getAllStackInfos() {
enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getAllStackInfos()");
@@ -2821,6 +2822,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
}
+ // TODO(148895075): deprecate and replace with task equivalents
@Override
public List<ActivityManager.StackInfo> getAllStackInfosOnDisplay(int displayId) {
enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getAllStackInfos()");
@@ -4153,7 +4155,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
return;
}
// Only update the saved args from the args that are set
- r.pictureInPictureArgs.copyOnlySet(params);
+ r.setPictureInPictureParams(params);
final float aspectRatio = r.pictureInPictureArgs.getAspectRatio();
final List<RemoteAction> actions = r.pictureInPictureArgs.getActions();
// Adjust the source bounds by the insets for the transition down
@@ -4201,7 +4203,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
"setPictureInPictureParams", token, params);
// Only update the saved args from the args that are set
- r.pictureInPictureArgs.copyOnlySet(params);
+ r.setPictureInPictureParams(params);
if (r.inPinnedWindowingMode()) {
// If the activity is already in picture-in-picture, update the pinned stack now
// if it is not already expanding to fullscreen. Otherwise, the arguments will
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
index 06e7b48a1f04..9e93e1455f2c 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
@@ -18,6 +18,9 @@ package com.android.server.wm;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import android.content.res.Resources;
+import android.text.TextUtils;
+
import com.android.server.wm.DisplayContent.TaskContainers;
/**
@@ -42,7 +45,18 @@ public abstract class DisplayAreaPolicy {
*/
protected final TaskContainers mTaskContainers;
- DisplayAreaPolicy(WindowManagerService wmService,
+ /**
+ * Construct a new {@link DisplayAreaPolicy}
+ *
+ * @param wmService the window manager service instance
+ * @param content the display content for which the policy applies
+ * @param root the root display area under which the policy operates
+ * @param imeContainer the ime container that the policy must attach
+ * @param taskContainers the task container that the policy must attach
+ *
+ * @see #attachDisplayAreas()
+ */
+ protected DisplayAreaPolicy(WindowManagerService wmService,
DisplayContent content, DisplayArea.Root root,
DisplayArea<? extends WindowContainer> imeContainer, TaskContainers taskContainers) {
mWmService = wmService;
@@ -119,5 +133,55 @@ public abstract class DisplayAreaPolicy {
throw new IllegalArgumentException("don't know how to sort " + token);
}
}
+
+ /** Provider for {@link DisplayAreaPolicy.Default platform-default display area policy}. */
+ static class Provider implements DisplayAreaPolicy.Provider {
+ @Override
+ public DisplayAreaPolicy instantiate(WindowManagerService wmService,
+ DisplayContent content, DisplayArea.Root root,
+ DisplayArea<? extends WindowContainer> imeContainer,
+ TaskContainers taskContainers) {
+ return new DisplayAreaPolicy.Default(wmService, content, root, imeContainer,
+ taskContainers);
+ }
+ }
+ }
+
+ /**
+ * Provider for {@link DisplayAreaPolicy} instances.
+ *
+ * By implementing this interface and overriding the
+ * {@code config_deviceSpecificDisplayAreaPolicyProvider}, a device-specific implementations
+ * of {@link DisplayAreaPolicy} can be supplied.
+ */
+ public interface Provider {
+ /**
+ * Instantiate a new DisplayAreaPolicy.
+ *
+ * @see DisplayAreaPolicy#DisplayAreaPolicy
+ */
+ DisplayAreaPolicy instantiate(WindowManagerService wmService,
+ DisplayContent content, DisplayArea.Root root,
+ DisplayArea<? extends WindowContainer> imeContainer,
+ TaskContainers taskContainers);
+
+ /**
+ * Instantiate the device-specific {@link Provider}.
+ */
+ static Provider fromResources(Resources res) {
+ String name = res.getString(
+ com.android.internal.R.string.config_deviceSpecificDisplayAreaPolicyProvider);
+ if (TextUtils.isEmpty(name)) {
+ return new DisplayAreaPolicy.Default.Provider();
+ }
+ try {
+ return (Provider) Class.forName(name).newInstance();
+ } catch (ReflectiveOperationException | ClassCastException e) {
+ throw new IllegalStateException("Couldn't instantiate class " + name
+ + " for config_deviceSpecificDisplayAreaPolicyProvider:"
+ + " make sure it has a public zero-argument constructor"
+ + " and implements DisplayAreaPolicy.Provider", e);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 810aa3438ea0..3b658c04b8c2 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -304,8 +304,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
private final DisplayArea.Root mRootDisplayArea = new DisplayArea.Root(mWmService);
- private final DisplayAreaPolicy mDisplayAreaPolicy = new DisplayAreaPolicy.Default(
- mWmService, this, mRootDisplayArea, mImeWindowsContainers, mTaskContainers);
+ private final DisplayAreaPolicy mDisplayAreaPolicy;
private WindowState mTmpWindow;
private WindowState mTmpWindow2;
@@ -1027,6 +1026,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
super.addChild(mWindowContainers, null);
super.addChild(mOverlayContainers, null);
+ mDisplayAreaPolicy = mWmService.mDisplayAreaPolicyProvider.instantiate(
+ mWmService, this, mRootDisplayArea, mImeWindowsContainers, mTaskContainers);
mWindowContainers.addChildren();
// Sets the display content for the children.
@@ -5659,7 +5660,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
return activityType == ACTIVITY_TYPE_STANDARD
&& (windowingMode == WINDOWING_MODE_FULLSCREEN
|| windowingMode == WINDOWING_MODE_FREEFORM
- || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+ || windowingMode == WINDOWING_MODE_MULTI_WINDOW);
}
/**
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index d0179adadbd7..51b9916159fe 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -44,6 +44,7 @@ import android.view.ViewRootImpl;
import android.view.WindowInsetsAnimationCallback;
import android.view.WindowInsetsAnimationControlListener;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.DisplayThread;
/**
@@ -107,11 +108,11 @@ class InsetsPolicy {
changed = true;
}
if (changed) {
- startAnimation(mShowingTransientTypes, true, () -> {
+ mPolicy.getStatusBarManagerInternal().showTransient(mDisplayContent.getDisplayId(),
+ mShowingTransientTypes.toArray());
+ updateBarControlTarget(mFocusedWin);
+ startAnimation(true /* show */, () -> {
synchronized (mDisplayContent.mWmService.mGlobalLock) {
- mPolicy.getStatusBarManagerInternal().showTransient(
- mDisplayContent.getDisplayId(),
- mShowingTransientTypes.toArray());
mStateController.notifyInsetsChanged();
}
});
@@ -122,7 +123,7 @@ class InsetsPolicy {
if (mShowingTransientTypes.size() == 0) {
return;
}
- startAnimation(mShowingTransientTypes, false, () -> {
+ startAnimation(false /* show */, () -> {
synchronized (mDisplayContent.mWmService.mGlobalLock) {
mShowingTransientTypes.clear();
mStateController.notifyInsetsChanged();
@@ -268,18 +269,20 @@ class InsetsPolicy {
return isDockedStackVisible || isFreeformStackVisible || isResizing;
}
- private void startAnimation(IntArray internalTypes, boolean show, Runnable callback) {
+ @VisibleForTesting
+ void startAnimation(boolean show, Runnable callback) {
int typesReady = 0;
final SparseArray<InsetsSourceControl> controls = new SparseArray<>();
- updateBarControlTarget(mFocusedWin);
- for (int i = internalTypes.size() - 1; i >= 0; i--) {
+ final IntArray showingTransientTypes = mShowingTransientTypes;
+ for (int i = showingTransientTypes.size() - 1; i >= 0; i--) {
InsetsSourceProvider provider =
- mStateController.getSourceProvider(internalTypes.get(i));
- if (provider == null) continue;
- InsetsSourceControl control = provider.getControl(provider.getControlTarget());
- if (control == null || control.getLeash() == null) continue;
- typesReady |= InsetsState.toPublicType(internalTypes.get(i));
- controls.put(control.getType(), control);
+ mStateController.getSourceProvider(showingTransientTypes.get(i));
+ InsetsSourceControl control = provider.getControl(mTransientControlTarget);
+ if (control == null || control.getLeash() == null) {
+ continue;
+ }
+ typesReady |= InsetsState.toPublicType(showingTransientTypes.get(i));
+ controls.put(control.getType(), new InsetsSourceControl(control));
}
controlAnimationUnchecked(typesReady, controls, show, callback);
}
@@ -335,7 +338,6 @@ class InsetsPolicy {
private InsetsPolicyAnimationControlListener mListener;
InsetsPolicyAnimationControlCallbacks(InsetsPolicyAnimationControlListener listener) {
- super();
mListener = listener;
}
@@ -353,9 +355,11 @@ class InsetsPolicy {
InsetsController.INTERPOLATOR, true,
show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN
: LAYOUT_INSETS_DURING_ANIMATION_HIDDEN);
+ SurfaceAnimationThread.getHandler().post(
+ () -> mListener.onReady(mAnimationControl, typesReady));
}
- /** Called on SurfaceAnimationThread lock without global WM lock held. */
+ /** Called on SurfaceAnimationThread without global WM lock held. */
@Override
public void scheduleApplyChangeInsets() {
InsetsState state = getState();
@@ -384,7 +388,7 @@ class InsetsPolicy {
return overrideState;
}
- /** Called on SurfaceAnimationThread lock without global WM lock held. */
+ /** Called on SurfaceAnimationThread without global WM lock held. */
@Override
public void applySurfaceParams(
final SyncRtSurfaceTransactionApplier.SurfaceParams... params) {
@@ -396,14 +400,12 @@ class InsetsPolicy {
t.apply();
}
- /** Called on SurfaceAnimationThread lock without global WM lock held. */
@Override
public void startAnimation(InsetsAnimationControlImpl controller,
WindowInsetsAnimationControlListener listener, int types,
WindowInsetsAnimationCallback.InsetsAnimation animation,
WindowInsetsAnimationCallback.AnimationBounds bounds,
int layoutDuringAnimation) {
- SurfaceAnimationThread.getHandler().post(() -> listener.onReady(controller, types));
}
}
}
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 798665972a33..0d3f6b98f483 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -59,6 +59,7 @@ class InsetsSourceProvider {
private final InsetsSourceControl mFakeControl;
private @Nullable InsetsSourceControl mControl;
private @Nullable InsetsControlTarget mControlTarget;
+ private @Nullable InsetsControlTarget mPendingControlTarget;
private @Nullable InsetsControlTarget mFakeControlTarget;
private @Nullable ControlAdapter mAdapter;
@@ -140,8 +141,9 @@ class InsetsSourceProvider {
mSource.setVisibleFrame(null);
} else if (mControllable) {
mWin.setControllableInsetProvider(this);
- if (mControlTarget != null) {
- updateControlForTarget(mControlTarget, true /* force */);
+ if (mPendingControlTarget != null) {
+ updateControlForTarget(mPendingControlTarget, true /* force */);
+ mPendingControlTarget = null;
}
}
}
@@ -245,7 +247,7 @@ class InsetsSourceProvider {
setWindow(null, null, null);
}
if (mWin == null) {
- mControlTarget = target;
+ mPendingControlTarget = target;
return;
}
if (target == mControlTarget && !force) {
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index caaa4305406c..2d7d3f18c101 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.view.InsetsState.ITYPE_CAPTION_BAR;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
@@ -89,6 +90,12 @@ class InsetsStateController {
if (type == ITYPE_NAVIGATION_BAR) {
state.removeSource(ITYPE_IME);
state.removeSource(ITYPE_STATUS_BAR);
+ state.removeSource(ITYPE_CAPTION_BAR);
+ }
+
+ // Status bar doesn't get influenced by caption bar
+ if (type == ITYPE_STATUS_BAR) {
+ state.removeSource(ITYPE_CAPTION_BAR);
}
// IME needs different frames for certain cases (e.g. navigation bar in gesture nav).
@@ -212,18 +219,18 @@ class InsetsStateController {
/**
* Called when the focused window that is able to control the system bars changes.
*
- * @param topControlling The target that is now able to control the top bar appearance
- * and visibility.
+ * @param statusControlling The target that is now able to control the status bar appearance
+ * and visibility.
* @param navControlling The target that is now able to control the nav bar appearance
* and visibility.
*/
- void onBarControlTargetChanged(@Nullable InsetsControlTarget topControlling,
- @Nullable InsetsControlTarget fakeTopControlling,
+ void onBarControlTargetChanged(@Nullable InsetsControlTarget statusControlling,
+ @Nullable InsetsControlTarget fakeStatusControlling,
@Nullable InsetsControlTarget navControlling,
@Nullable InsetsControlTarget fakeNavControlling) {
- onControlChanged(ITYPE_STATUS_BAR, topControlling);
+ onControlChanged(ITYPE_STATUS_BAR, statusControlling);
onControlChanged(ITYPE_NAVIGATION_BAR, navControlling);
- onControlFakeTargetChanged(ITYPE_STATUS_BAR, fakeTopControlling);
+ onControlFakeTargetChanged(ITYPE_STATUS_BAR, fakeStatusControlling);
onControlFakeTargetChanged(ITYPE_NAVIGATION_BAR, fakeNavControlling);
notifyPendingInsetsControlChanged();
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 302e7596dec4..326335e6df46 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -99,6 +99,7 @@ import android.app.ActivityManager.TaskSnapshot;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.AppGlobals;
+import android.app.PictureInPictureParams;
import android.app.TaskInfo;
import android.app.WindowConfiguration;
import android.content.ComponentName;
@@ -461,6 +462,11 @@ class Task extends WindowContainer<WindowContainer> {
*/
ITaskOrganizer mTaskOrganizer;
+ /**
+ * Last Picture-in-Picture params applicable to the task. Updated when the app
+ * enters Picture-in-Picture or when setPictureInPictureParams is called.
+ */
+ PictureInPictureParams mPictureInPictureParams = new PictureInPictureParams.Builder().build();
/**
* Don't use constructor directly. Use {@link #create(ActivityTaskManagerService, int,
@@ -3217,6 +3223,12 @@ class Task extends WindowContainer<WindowContainer> {
// order changes.
final Task top = getTopMostTask();
info.resizeMode = top != null ? top.mResizeMode : mResizeMode;
+
+ if (mPictureInPictureParams.empty()) {
+ info.pictureInPictureParams = null;
+ } else {
+ info.pictureInPictureParams = mPictureInPictureParams;
+ }
}
/**
@@ -3947,4 +3959,10 @@ class Task extends WindowContainer<WindowContainer> {
void onWindowFocusChanged(boolean hasFocus) {
updateShadowsRadius(hasFocus, getPendingTransaction());
}
+
+ void setPictureInPictureParams(PictureInPictureParams p) {
+ mPictureInPictureParams.copyOnlySet(p);
+ mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(
+ this, true /* force */);
+ }
}
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 0a0530c92a16..4b13a0c1f75d 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -43,6 +43,7 @@ import android.view.IWindowContainer;
import android.view.SurfaceControl;
import android.view.WindowContainerTransaction;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.function.pooled.PooledConsumer;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -379,7 +380,8 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub
}
@Override
- public List<RunningTaskInfo> getChildTasks(IWindowContainer parent) {
+ public List<RunningTaskInfo> getChildTasks(IWindowContainer parent,
+ @Nullable int[] activityTypes) {
enforceStackPermission("getChildTasks()");
final long ident = Binder.clearCallingIdentity();
try {
@@ -405,6 +407,10 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub
for (int i = dc.getStackCount() - 1; i >= 0; --i) {
final ActivityStack as = dc.getStackAt(i);
if (as.getTile() == container) {
+ if (activityTypes != null
+ && !ArrayUtils.contains(activityTypes, as.getActivityType())) {
+ continue;
+ }
final RunningTaskInfo info = new RunningTaskInfo();
as.fillTaskInfo(info);
out.add(info);
@@ -417,6 +423,40 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub
}
}
+ @Override
+ public List<RunningTaskInfo> getRootTasks(int displayId, @Nullable int[] activityTypes) {
+ enforceStackPermission("getRootTasks()");
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ final DisplayContent dc =
+ mService.mRootWindowContainer.getDisplayContent(displayId);
+ if (dc == null) {
+ throw new IllegalArgumentException("Display " + displayId + " doesn't exist");
+ }
+ ArrayList<RunningTaskInfo> out = new ArrayList<>();
+ for (int i = dc.getStackCount() - 1; i >= 0; --i) {
+ final ActivityStack task = dc.getStackAt(i);
+ if (task.getTile() != null) {
+ // a tile is supposed to look like a parent, so don't include their
+ // "children" here. They can be accessed via getChildTasks()
+ continue;
+ }
+ if (activityTypes != null
+ && !ArrayUtils.contains(activityTypes, task.getActivityType())) {
+ continue;
+ }
+ final RunningTaskInfo info = new RunningTaskInfo();
+ task.fillTaskInfo(info);
+ out.add(info);
+ }
+ return out;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
private int sanitizeAndApplyChange(WindowContainer container,
WindowContainerTransaction.Change change) {
if (!(container instanceof Task)) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index a370093a3907..d98c18c077b8 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -187,6 +187,7 @@ import android.os.SystemService;
import android.os.Trace;
import android.os.UserHandle;
import android.os.WorkSource;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
@@ -309,6 +310,8 @@ public class WindowManagerService extends IWindowManager.Stub
implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowManagerService" : TAG_WM;
+ private static final String WM_USE_BLAST_ADAPTER_FLAG = "wm_use_blast_adapter";
+
static final int LAYOUT_REPEAT_THRESHOLD = 4;
static final boolean PROFILE_ORIENTATION = false;
@@ -413,6 +416,8 @@ public class WindowManagerService extends IWindowManager.Stub
final WindowTracing mWindowTracing;
+ final DisplayAreaPolicy.Provider mDisplayAreaPolicyProvider;
+
final private KeyguardDisableHandler mKeyguardDisableHandler;
// TODO: eventually unify all keyguard state in a common place instead of having it spread over
// AM's KeyguardController and the policy's KeyguardServiceDelegate.
@@ -623,6 +628,9 @@ public class WindowManagerService extends IWindowManager.Stub
// The root of the device window hierarchy.
RootWindowContainer mRoot;
+ // Whether the system should use BLAST for ViewRootImpl
+ final boolean mUseBLAST;
+
int mDockedStackCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
Rect mDockedStackCreateBounds;
@@ -1137,6 +1145,10 @@ public class WindowManagerService extends IWindowManager.Stub
mAnimator = new WindowAnimator(this);
mRoot = new RootWindowContainer(this);
+ mUseBLAST = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT,
+ WM_USE_BLAST_ADAPTER_FLAG, false);
+
mWindowPlacerLocked = new WindowSurfacePlacer(this);
mTaskSnapshotController = new TaskSnapshotController(this);
@@ -1255,6 +1267,10 @@ public class WindowManagerService extends IWindowManager.Stub
LocalServices.addService(WindowManagerInternal.class, new LocalService());
mEmbeddedWindowController = new EmbeddedWindowController(mGlobalLock);
+
+ mDisplayAreaPolicyProvider = DisplayAreaPolicy.Provider.fromResources(
+ mContext.getResources());
+
setGlobalShadowSettings();
}
@@ -5051,6 +5067,11 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
+ public boolean useBLAST() {
+ return mUseBLAST;
+ }
+
+ @Override
public void getInitialDisplaySize(int displayId, Point size) {
synchronized (mGlobalLock) {
final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 109f39d9dbcf..cb687c9144c6 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -553,6 +553,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
private static final long ADMIN_APP_PASSWORD_COMPLEXITY = 123562444L;
+ /**
+ * Admin apps targeting Android R+ may not use
+ * {@link android.app.admin.DevicePolicyManager#setSecureSetting} to change the deprecated
+ * {@link android.provider.Settings.Secure#LOCATION_MODE} setting. Instead they should use
+ * {@link android.app.admin.DevicePolicyManager#setLocationEnabled}.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ private static final long USE_SET_LOCATION_ENABLED = 117835097L;
+
final Context mContext;
final Injector mInjector;
final IPackageManager mIPackageManager;
@@ -11554,13 +11564,22 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public void setLocationEnabled(ComponentName who, boolean locationEnabled) {
- Objects.requireNonNull(who, "ComponentName is null");
- enforceDeviceOwner(who);
+ enforceDeviceOwner(Objects.requireNonNull(who));
- UserHandle userHandle = mInjector.binderGetCallingUserHandle();
- mInjector.binderWithCleanCallingIdentity(
- () -> mInjector.getLocationManager().setLocationEnabledForUser(locationEnabled,
- userHandle));
+ UserHandle user = mInjector.binderGetCallingUserHandle();
+
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ boolean wasLocationEnabled = mInjector.getLocationManager().isLocationEnabledForUser(
+ user);
+ mInjector.getLocationManager().setLocationEnabledForUser(locationEnabled, user);
+
+ // make a best effort to only show the notification if the admin is actually changing
+ // something. this is subject to race conditions with settings changes, but those are
+ // unlikely to realistically interfere
+ if (wasLocationEnabled != locationEnabled) {
+ showLocationSettingsChangedNotification(user);
+ }
+ });
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_SECURE_SETTING)
@@ -11571,6 +11590,25 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
.write();
}
+ private void showLocationSettingsChangedNotification(UserHandle user) {
+ PendingIntent locationSettingsIntent = mInjector.pendingIntentGetActivityAsUser(mContext, 0,
+ new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), PendingIntent.FLAG_UPDATE_CURRENT,
+ null, user);
+ Notification notification = new Notification.Builder(mContext,
+ SystemNotificationChannels.DEVICE_ADMIN)
+ .setSmallIcon(R.drawable.ic_info_outline)
+ .setContentTitle(mContext.getString(R.string.location_changed_notification_title))
+ .setContentText(mContext.getString(R.string.location_changed_notification_text))
+ .setColor(mContext.getColor(R.color.system_notification_accent_color))
+ .setShowWhen(true)
+ .setContentIntent(locationSettingsIntent)
+ .setAutoCancel(true)
+ .build();
+ mInjector.getNotificationManager().notify(SystemMessage.NOTE_LOCATION_CHANGED,
+ notification);
+ }
+
@Override
public void requestSetLocationProviderAllowed(ComponentName who, String provider,
boolean providerAllowed) {
@@ -11641,6 +11679,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
throw new SecurityException(String.format(
"Permission denial: Profile owners cannot update %1$s", setting));
}
+ if (setting.equals(Settings.Secure.LOCATION_MODE)
+ && isSetSecureSettingLocationModeCheckEnabled(who.getPackageName(),
+ callingUserId)) {
+ throw new UnsupportedOperationException(Settings.Secure.LOCATION_MODE + " is "
+ + "deprecated. Please use setLocationEnabled() instead.");
+ }
if (setting.equals(Settings.Secure.INSTALL_NON_MARKET_APPS)) {
if (getTargetSdk(who.getPackageName(), callingUserId) >= Build.VERSION_CODES.O) {
throw new UnsupportedOperationException(Settings.Secure.INSTALL_NON_MARKET_APPS
@@ -11685,6 +11729,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
saveSettingsLocked(callingUserId);
}
mInjector.settingsSecurePutStringForUser(setting, value, callingUserId);
+ if (setting.equals(Settings.Secure.LOCATION_MODE)) {
+ showLocationSettingsChangedNotification(UserHandle.of(callingUserId));
+ }
});
}
DevicePolicyEventLogger
@@ -11694,6 +11741,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
.write();
}
+ private boolean isSetSecureSettingLocationModeCheckEnabled(String packageName, int userId) {
+ try {
+ return mIPlatformCompat.isChangeEnabledByPackageName(USE_SET_LOCATION_ENABLED,
+ packageName, userId);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Failed to get a response from PLATFORM_COMPAT_SERVICE", e);
+ return getTargetSdk(packageName, userId) > Build.VERSION_CODES.Q;
+ }
+ }
+
@Override
public void setMasterVolumeMuted(ComponentName who, boolean on) {
Objects.requireNonNull(who, "ComponentName is null");
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 3b513774b615..8012e74a6b3c 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -1180,7 +1180,7 @@ bool IncrementalService::configureNativeBinaries(StorageId storage, std::string_
}
// Create new lib file without signature info
- incfs::NewFileParams libFileParams;
+ incfs::NewFileParams libFileParams{};
libFileParams.size = uncompressedLen;
libFileParams.verification.hashAlgorithm = INCFS_HASH_NONE;
// Metadata of the new lib file is its relative path
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index 2444ddecdb80..2e7ced37e91d 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -122,7 +122,6 @@ public:
}
RawMetadata getMetadata(StorageId storage, FileId node) const;
- std::string getSignatureData(StorageId storage, FileId node) const;
std::vector<std::string> listFiles(StorageId storage) const;
bool startLoading(StorageId storage) const;
diff --git a/services/people/java/com/android/server/people/data/AbstractProtoDiskReadWriter.java b/services/people/java/com/android/server/people/data/AbstractProtoDiskReadWriter.java
new file mode 100644
index 000000000000..203e9804bfa3
--- /dev/null
+++ b/services/people/java/com/android/server/people/data/AbstractProtoDiskReadWriter.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.people.data;
+
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.WorkerThread;
+import android.text.format.DateUtils;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Base class for reading and writing protobufs on disk from a root directory. Callers should
+ * ensure that the root directory is unlocked before doing I/O operations using this class.
+ *
+ * @param <T> is the data class representation of a protobuf.
+ */
+abstract class AbstractProtoDiskReadWriter<T> {
+
+ private static final String TAG = AbstractProtoDiskReadWriter.class.getSimpleName();
+ private static final long SHUTDOWN_DISK_WRITE_TIMEOUT = 5L * DateUtils.SECOND_IN_MILLIS;
+
+ private final File mRootDir;
+ private final ScheduledExecutorService mScheduledExecutorService;
+ private final long mWriteDelayMs;
+
+ @GuardedBy("this")
+ private ScheduledFuture<?> mScheduledFuture;
+
+ @GuardedBy("this")
+ private Map<String, T> mScheduledFileDataMap = new ArrayMap<>();
+
+ /**
+ * Child class shall provide a {@link ProtoStreamWriter} to facilitate the writing of data as a
+ * protobuf on disk.
+ */
+ abstract ProtoStreamWriter<T> protoStreamWriter();
+
+ /**
+ * Child class shall provide a {@link ProtoStreamReader} to facilitate the reading of protobuf
+ * data on disk.
+ */
+ abstract ProtoStreamReader<T> protoStreamReader();
+
+ AbstractProtoDiskReadWriter(@NonNull File rootDir, long writeDelayMs,
+ @NonNull ScheduledExecutorService scheduledExecutorService) {
+ mRootDir = rootDir;
+ mWriteDelayMs = writeDelayMs;
+ mScheduledExecutorService = scheduledExecutorService;
+ }
+
+ @WorkerThread
+ void delete(@NonNull String fileName) {
+ final File file = getFile(fileName);
+ if (!file.exists()) {
+ return;
+ }
+ if (!file.delete()) {
+ Slog.e(TAG, "Failed to delete file: " + file.getPath());
+ }
+ }
+
+ @WorkerThread
+ void writeTo(@NonNull String fileName, @NonNull T data) {
+ final File file = getFile(fileName);
+ final AtomicFile atomicFile = new AtomicFile(file);
+
+ FileOutputStream fileOutputStream = null;
+ try {
+ fileOutputStream = atomicFile.startWrite();
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to write to protobuf file.", e);
+ return;
+ }
+
+ try {
+ final ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream);
+ protoStreamWriter().write(protoOutputStream, data);
+ protoOutputStream.flush();
+ atomicFile.finishWrite(fileOutputStream);
+ fileOutputStream = null;
+ } finally {
+ // When fileInputStream is null (successful write), this will no-op.
+ atomicFile.failWrite(fileOutputStream);
+ }
+ }
+
+ @WorkerThread
+ @Nullable
+ T read(@NonNull String fileName) {
+ File[] files = mRootDir.listFiles(
+ pathname -> pathname.isFile() && pathname.getName().equals(fileName));
+ if (files == null || files.length == 0) {
+ return null;
+ } else if (files.length > 1) {
+ // This can't possibly happen, but sanity check.
+ Slog.w(TAG, "Found multiple files with the same name: " + Arrays.toString(files));
+ }
+ return parseFile(files[0]);
+ }
+
+ /**
+ * Reads all files in directory and returns a map with file names as keys and parsed file
+ * contents as values.
+ */
+ @WorkerThread
+ @Nullable
+ Map<String, T> readAll() {
+ File[] files = mRootDir.listFiles(File::isFile);
+ if (files == null) {
+ return null;
+ }
+
+ Map<String, T> results = new ArrayMap<>();
+ for (File file : files) {
+ T result = parseFile(file);
+ if (result != null) {
+ results.put(file.getName(), result);
+ }
+ }
+ return results;
+ }
+
+ /**
+ * Schedules the specified data to be flushed to a file in the future. Subsequent
+ * calls for the same file before the flush occurs will replace the previous data but will not
+ * reset when the flush will occur. All unique files will be flushed at the same time.
+ */
+ @MainThread
+ synchronized void scheduleSave(@NonNull String fileName, @NonNull T data) {
+ mScheduledFileDataMap.put(fileName, data);
+
+ if (mScheduledExecutorService.isShutdown()) {
+ Slog.e(TAG, "Worker is shutdown, failed to schedule data saving.");
+ return;
+ }
+
+ // Skip scheduling another flush when one is pending.
+ if (mScheduledFuture != null) {
+ return;
+ }
+
+ mScheduledFuture = mScheduledExecutorService.schedule(this::flushScheduledData,
+ mWriteDelayMs, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Saves specified data immediately on a background thread, and blocks until its completed. This
+ * is useful for when device is powering off.
+ */
+ @MainThread
+ synchronized void saveImmediately(@NonNull String fileName, @NonNull T data) {
+ if (mScheduledExecutorService.isShutdown()) {
+ return;
+ }
+ // Cancel existing future.
+ if (mScheduledFuture != null) {
+
+ // We shouldn't need to interrupt as this method and threaded task
+ // #flushScheduledData are both synchronized.
+ mScheduledFuture.cancel(true);
+ }
+
+ mScheduledFileDataMap.put(fileName, data);
+ // Submit flush and blocks until it completes. Blocking will prevent the device from
+ // shutting down before flushing completes.
+ Future<?> future = mScheduledExecutorService.submit(this::flushScheduledData);
+ try {
+ future.get(SHUTDOWN_DISK_WRITE_TIMEOUT, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ Slog.e(TAG, "Failed to save data immediately.", e);
+ }
+ }
+
+ @WorkerThread
+ private synchronized void flushScheduledData() {
+ if (mScheduledFileDataMap.isEmpty()) {
+ mScheduledFuture = null;
+ return;
+ }
+ for (String fileName : mScheduledFileDataMap.keySet()) {
+ T data = mScheduledFileDataMap.remove(fileName);
+ writeTo(fileName, data);
+ }
+ mScheduledFuture = null;
+ }
+
+ @WorkerThread
+ @Nullable
+ private T parseFile(@NonNull File file) {
+ final AtomicFile atomicFile = new AtomicFile(file);
+ try (FileInputStream fileInputStream = atomicFile.openRead()) {
+ final ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream);
+ return protoStreamReader().read(protoInputStream);
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to parse protobuf file.", e);
+ }
+ return null;
+ }
+
+ @NonNull
+ private File getFile(String fileName) {
+ return new File(mRootDir, fileName);
+ }
+
+ /**
+ * {@code ProtoStreamWriter} writes {@code T} fields to {@link ProtoOutputStream}.
+ *
+ * @param <T> is the data class representation of a protobuf.
+ */
+ interface ProtoStreamWriter<T> {
+
+ /**
+ * Writes {@code T} to {@link ProtoOutputStream}.
+ */
+ void write(@NonNull ProtoOutputStream protoOutputStream, @NonNull T data);
+ }
+
+ /**
+ * {@code ProtoStreamReader} reads {@link ProtoInputStream} and translate it to {@code T}.
+ *
+ * @param <T> is the data class representation of a protobuf.
+ */
+ interface ProtoStreamReader<T> {
+ /**
+ * Reads {@link ProtoInputStream} and translates it to {@code T}.
+ */
+ @Nullable
+ T read(@NonNull ProtoInputStream protoInputStream);
+ }
+}
diff --git a/services/people/java/com/android/server/people/data/ConversationInfo.java b/services/people/java/com/android/server/people/data/ConversationInfo.java
index b60ed3e8783f..ce353667b62d 100644
--- a/services/people/java/com/android/server/people/data/ConversationInfo.java
+++ b/services/people/java/com/android/server/people/data/ConversationInfo.java
@@ -20,12 +20,18 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.LocusId;
+import android.content.LocusIdProto;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutInfo.ShortcutFlags;
import android.net.Uri;
+import android.util.Slog;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.util.Preconditions;
+import com.android.server.people.ConversationInfoProto;
+import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
@@ -35,6 +41,8 @@ import java.util.Objects;
*/
public class ConversationInfo {
+ private static final String TAG = ConversationInfo.class.getSimpleName();
+
private static final int FLAG_IMPORTANT = 1;
private static final int FLAG_NOTIFICATION_SILENCED = 1 << 1;
@@ -241,6 +249,72 @@ public class ConversationInfo {
return (mConversationFlags & flags) == flags;
}
+ /** Writes field members to {@link ProtoOutputStream}. */
+ void writeToProto(@NonNull ProtoOutputStream protoOutputStream) {
+ protoOutputStream.write(ConversationInfoProto.SHORTCUT_ID, mShortcutId);
+ if (mLocusId != null) {
+ long locusIdToken = protoOutputStream.start(ConversationInfoProto.LOCUS_ID_PROTO);
+ protoOutputStream.write(LocusIdProto.LOCUS_ID, mLocusId.getId());
+ protoOutputStream.end(locusIdToken);
+ }
+ if (mContactUri != null) {
+ protoOutputStream.write(ConversationInfoProto.CONTACT_URI, mContactUri.toString());
+ }
+ if (mNotificationChannelId != null) {
+ protoOutputStream.write(ConversationInfoProto.NOTIFICATION_CHANNEL_ID,
+ mNotificationChannelId);
+ }
+ protoOutputStream.write(ConversationInfoProto.SHORTCUT_FLAGS, mShortcutFlags);
+ protoOutputStream.write(ConversationInfoProto.CONVERSATION_FLAGS, mConversationFlags);
+ }
+
+ /** Reads from {@link ProtoInputStream} and constructs a {@link ConversationInfo}. */
+ @NonNull
+ static ConversationInfo readFromProto(@NonNull ProtoInputStream protoInputStream)
+ throws IOException {
+ ConversationInfo.Builder builder = new ConversationInfo.Builder();
+ while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (protoInputStream.getFieldNumber()) {
+ case (int) ConversationInfoProto.SHORTCUT_ID:
+ builder.setShortcutId(
+ protoInputStream.readString(ConversationInfoProto.SHORTCUT_ID));
+ break;
+ case (int) ConversationInfoProto.LOCUS_ID_PROTO:
+ long locusIdToken = protoInputStream.start(
+ ConversationInfoProto.LOCUS_ID_PROTO);
+ while (protoInputStream.nextField()
+ != ProtoInputStream.NO_MORE_FIELDS) {
+ if (protoInputStream.getFieldNumber() == (int) LocusIdProto.LOCUS_ID) {
+ builder.setLocusId(new LocusId(
+ protoInputStream.readString(LocusIdProto.LOCUS_ID)));
+ }
+ }
+ protoInputStream.end(locusIdToken);
+ break;
+ case (int) ConversationInfoProto.CONTACT_URI:
+ builder.setContactUri(Uri.parse(protoInputStream.readString(
+ ConversationInfoProto.CONTACT_URI)));
+ break;
+ case (int) ConversationInfoProto.NOTIFICATION_CHANNEL_ID:
+ builder.setNotificationChannelId(protoInputStream.readString(
+ ConversationInfoProto.NOTIFICATION_CHANNEL_ID));
+ break;
+ case (int) ConversationInfoProto.SHORTCUT_FLAGS:
+ builder.setShortcutFlags(protoInputStream.readInt(
+ ConversationInfoProto.SHORTCUT_FLAGS));
+ break;
+ case (int) ConversationInfoProto.CONVERSATION_FLAGS:
+ builder.setConversationFlags(protoInputStream.readInt(
+ ConversationInfoProto.CONVERSATION_FLAGS));
+ break;
+ default:
+ Slog.w(TAG, "Could not read undefined field: "
+ + protoInputStream.getFieldNumber());
+ }
+ }
+ return builder.build();
+ }
+
/**
* Builder class for {@link ConversationInfo} objects.
*/
diff --git a/services/people/java/com/android/server/people/data/ConversationStore.java b/services/people/java/com/android/server/people/data/ConversationStore.java
index 364992181f75..ea36d38e5d4a 100644
--- a/services/people/java/com/android/server/people/data/ConversationStore.java
+++ b/services/people/java/com/android/server/people/data/ConversationStore.java
@@ -16,58 +16,124 @@
package com.android.server.people.data;
+import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.WorkerThread;
import android.content.LocusId;
import android.net.Uri;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
import android.util.ArrayMap;
+import android.util.Slog;
+import android.util.proto.ProtoInputStream;
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.people.ConversationInfosProto;
+
+import com.google.android.collect.Lists;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
+import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Consumer;
-/** The store that stores and accesses the conversations data for a package. */
+/**
+ * The store that stores and accesses the conversations data for a package.
+ */
class ConversationStore {
+ private static final String TAG = ConversationStore.class.getSimpleName();
+
+ private static final String CONVERSATIONS_FILE_NAME = "conversations";
+
+ private static final long DISK_WRITE_DELAY = 2L * DateUtils.MINUTE_IN_MILLIS;
+
// Shortcut ID -> Conversation Info
+ @GuardedBy("this")
private final Map<String, ConversationInfo> mConversationInfoMap = new ArrayMap<>();
// Locus ID -> Shortcut ID
+ @GuardedBy("this")
private final Map<LocusId, String> mLocusIdToShortcutIdMap = new ArrayMap<>();
// Contact URI -> Shortcut ID
+ @GuardedBy("this")
private final Map<Uri, String> mContactUriToShortcutIdMap = new ArrayMap<>();
// Phone Number -> Shortcut ID
+ @GuardedBy("this")
private final Map<String, String> mPhoneNumberToShortcutIdMap = new ArrayMap<>();
// Notification Channel ID -> Shortcut ID
+ @GuardedBy("this")
private final Map<String, String> mNotifChannelIdToShortcutIdMap = new ArrayMap<>();
- void addOrUpdate(@NonNull ConversationInfo conversationInfo) {
- mConversationInfoMap.put(conversationInfo.getShortcutId(), conversationInfo);
+ private final ScheduledExecutorService mScheduledExecutorService;
+ private final File mPackageDir;
+ private final ContactsQueryHelper mHelper;
- LocusId locusId = conversationInfo.getLocusId();
- if (locusId != null) {
- mLocusIdToShortcutIdMap.put(locusId, conversationInfo.getShortcutId());
- }
+ private ConversationInfosProtoDiskReadWriter mConversationInfosProtoDiskReadWriter;
- Uri contactUri = conversationInfo.getContactUri();
- if (contactUri != null) {
- mContactUriToShortcutIdMap.put(contactUri, conversationInfo.getShortcutId());
- }
+ ConversationStore(@NonNull File packageDir,
+ @NonNull ScheduledExecutorService scheduledExecutorService,
+ @NonNull ContactsQueryHelper helper) {
+ mScheduledExecutorService = scheduledExecutorService;
+ mPackageDir = packageDir;
+ mHelper = helper;
+ }
- String phoneNumber = conversationInfo.getContactPhoneNumber();
- if (phoneNumber != null) {
- mPhoneNumberToShortcutIdMap.put(phoneNumber, conversationInfo.getShortcutId());
- }
+ /**
+ * Loads conversations from disk to memory in a background thread. This should be called
+ * after the device powers on and the user has been unlocked.
+ */
+ @MainThread
+ void loadConversationsFromDisk() {
+ mScheduledExecutorService.submit(() -> {
+ synchronized (this) {
+ ConversationInfosProtoDiskReadWriter conversationInfosProtoDiskReadWriter =
+ getConversationInfosProtoDiskReadWriter();
+ if (conversationInfosProtoDiskReadWriter == null) {
+ return;
+ }
+ List<ConversationInfo> conversationsOnDisk =
+ conversationInfosProtoDiskReadWriter.read(CONVERSATIONS_FILE_NAME);
+ if (conversationsOnDisk == null) {
+ return;
+ }
+ for (ConversationInfo conversationInfo : conversationsOnDisk) {
+ conversationInfo = restoreConversationPhoneNumber(conversationInfo);
+ updateConversationsInMemory(conversationInfo);
+ }
+ }
+ });
+ }
- String notifChannelId = conversationInfo.getNotificationChannelId();
- if (notifChannelId != null) {
- mNotifChannelIdToShortcutIdMap.put(notifChannelId, conversationInfo.getShortcutId());
+ /**
+ * Immediately flushes current conversations to disk. This should be called when device is
+ * powering off.
+ */
+ @MainThread
+ synchronized void saveConversationsToDisk() {
+ ConversationInfosProtoDiskReadWriter conversationInfosProtoDiskReadWriter =
+ getConversationInfosProtoDiskReadWriter();
+ if (conversationInfosProtoDiskReadWriter != null) {
+ conversationInfosProtoDiskReadWriter.saveConversationsImmediately(
+ new ArrayList<>(mConversationInfoMap.values()));
}
}
- void deleteConversation(@NonNull String shortcutId) {
+ @MainThread
+ synchronized void addOrUpdate(@NonNull ConversationInfo conversationInfo) {
+ updateConversationsInMemory(conversationInfo);
+ scheduleUpdateConversationsOnDisk();
+ }
+
+ @MainThread
+ synchronized void deleteConversation(@NonNull String shortcutId) {
ConversationInfo conversationInfo = mConversationInfoMap.remove(shortcutId);
if (conversationInfo == null) {
return;
@@ -92,31 +158,32 @@ class ConversationStore {
if (notifChannelId != null) {
mNotifChannelIdToShortcutIdMap.remove(notifChannelId);
}
+ scheduleUpdateConversationsOnDisk();
}
- void forAllConversations(@NonNull Consumer<ConversationInfo> consumer) {
+ synchronized void forAllConversations(@NonNull Consumer<ConversationInfo> consumer) {
for (ConversationInfo ci : mConversationInfoMap.values()) {
consumer.accept(ci);
}
}
@Nullable
- ConversationInfo getConversation(@Nullable String shortcutId) {
+ synchronized ConversationInfo getConversation(@Nullable String shortcutId) {
return shortcutId != null ? mConversationInfoMap.get(shortcutId) : null;
}
@Nullable
- ConversationInfo getConversationByLocusId(@NonNull LocusId locusId) {
+ synchronized ConversationInfo getConversationByLocusId(@NonNull LocusId locusId) {
return getConversation(mLocusIdToShortcutIdMap.get(locusId));
}
@Nullable
- ConversationInfo getConversationByContactUri(@NonNull Uri contactUri) {
+ synchronized ConversationInfo getConversationByContactUri(@NonNull Uri contactUri) {
return getConversation(mContactUriToShortcutIdMap.get(contactUri));
}
@Nullable
- ConversationInfo getConversationByPhoneNumber(@NonNull String phoneNumber) {
+ synchronized ConversationInfo getConversationByPhoneNumber(@NonNull String phoneNumber) {
return getConversation(mPhoneNumberToShortcutIdMap.get(phoneNumber));
}
@@ -124,4 +191,140 @@ class ConversationStore {
ConversationInfo getConversationByNotificationChannelId(@NonNull String notifChannelId) {
return getConversation(mNotifChannelIdToShortcutIdMap.get(notifChannelId));
}
+
+ @MainThread
+ private synchronized void updateConversationsInMemory(
+ @NonNull ConversationInfo conversationInfo) {
+ mConversationInfoMap.put(conversationInfo.getShortcutId(), conversationInfo);
+
+ LocusId locusId = conversationInfo.getLocusId();
+ if (locusId != null) {
+ mLocusIdToShortcutIdMap.put(locusId, conversationInfo.getShortcutId());
+ }
+
+ Uri contactUri = conversationInfo.getContactUri();
+ if (contactUri != null) {
+ mContactUriToShortcutIdMap.put(contactUri, conversationInfo.getShortcutId());
+ }
+
+ String phoneNumber = conversationInfo.getContactPhoneNumber();
+ if (phoneNumber != null) {
+ mPhoneNumberToShortcutIdMap.put(phoneNumber, conversationInfo.getShortcutId());
+ }
+
+ String notifChannelId = conversationInfo.getNotificationChannelId();
+ if (notifChannelId != null) {
+ mNotifChannelIdToShortcutIdMap.put(notifChannelId, conversationInfo.getShortcutId());
+ }
+ }
+
+ /** Schedules a dump of all conversations onto disk, overwriting existing values. */
+ @MainThread
+ private synchronized void scheduleUpdateConversationsOnDisk() {
+ ConversationInfosProtoDiskReadWriter conversationInfosProtoDiskReadWriter =
+ getConversationInfosProtoDiskReadWriter();
+ if (conversationInfosProtoDiskReadWriter != null) {
+ conversationInfosProtoDiskReadWriter.scheduleConversationsSave(
+ new ArrayList<>(mConversationInfoMap.values()));
+ }
+ }
+
+ @Nullable
+ private ConversationInfosProtoDiskReadWriter getConversationInfosProtoDiskReadWriter() {
+ if (!mPackageDir.exists()) {
+ Slog.e(TAG, "Package data directory does not exist: " + mPackageDir.getAbsolutePath());
+ return null;
+ }
+ if (mConversationInfosProtoDiskReadWriter == null) {
+ mConversationInfosProtoDiskReadWriter = new ConversationInfosProtoDiskReadWriter(
+ mPackageDir, CONVERSATIONS_FILE_NAME, DISK_WRITE_DELAY,
+ mScheduledExecutorService);
+ }
+ return mConversationInfosProtoDiskReadWriter;
+ }
+
+ /**
+ * Conversation's phone number is not saved on disk, so it has to be fetched.
+ */
+ @WorkerThread
+ private ConversationInfo restoreConversationPhoneNumber(
+ @NonNull ConversationInfo conversationInfo) {
+ if (conversationInfo.getContactUri() != null) {
+ if (mHelper.query(conversationInfo.getContactUri().toString())) {
+ String phoneNumber = mHelper.getPhoneNumber();
+ if (!TextUtils.isEmpty(phoneNumber)) {
+ conversationInfo = new ConversationInfo.Builder(
+ conversationInfo).setContactPhoneNumber(
+ phoneNumber).build();
+ }
+ }
+ }
+ return conversationInfo;
+ }
+
+ /** Reads and writes {@link ConversationInfo} on disk. */
+ static class ConversationInfosProtoDiskReadWriter extends
+ AbstractProtoDiskReadWriter<List<ConversationInfo>> {
+
+ private final String mConversationInfoFileName;
+
+ ConversationInfosProtoDiskReadWriter(@NonNull File baseDir,
+ @NonNull String conversationInfoFileName,
+ long writeDelayMs, @NonNull ScheduledExecutorService scheduledExecutorService) {
+ super(baseDir, writeDelayMs, scheduledExecutorService);
+ mConversationInfoFileName = conversationInfoFileName;
+ }
+
+ @Override
+ ProtoStreamWriter<List<ConversationInfo>> protoStreamWriter() {
+ return (protoOutputStream, data) -> {
+ for (ConversationInfo conversationInfo : data) {
+ long token = protoOutputStream.start(ConversationInfosProto.CONVERSATION_INFOS);
+ conversationInfo.writeToProto(protoOutputStream);
+ protoOutputStream.end(token);
+ }
+ };
+ }
+
+ @Override
+ ProtoStreamReader<List<ConversationInfo>> protoStreamReader() {
+ return protoInputStream -> {
+ List<ConversationInfo> results = Lists.newArrayList();
+ try {
+ while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ if (protoInputStream.getFieldNumber()
+ != (int) ConversationInfosProto.CONVERSATION_INFOS) {
+ continue;
+ }
+ long token = protoInputStream.start(
+ ConversationInfosProto.CONVERSATION_INFOS);
+ ConversationInfo conversationInfo = ConversationInfo.readFromProto(
+ protoInputStream);
+ protoInputStream.end(token);
+ results.add(conversationInfo);
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to read protobuf input stream.", e);
+ }
+ return results;
+ };
+ }
+
+ /**
+ * Schedules a flush of the specified conversations to disk.
+ */
+ @MainThread
+ void scheduleConversationsSave(@NonNull List<ConversationInfo> conversationInfos) {
+ scheduleSave(mConversationInfoFileName, conversationInfos);
+ }
+
+ /**
+ * Saves the specified conversations immediately. This should be used when device is
+ * powering off.
+ */
+ @MainThread
+ void saveConversationsImmediately(@NonNull List<ConversationInfo> conversationInfos) {
+ saveImmediately(mConversationInfoFileName, conversationInfos);
+ }
+ }
}
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index 7a3ed5348d30..c8673f833e71 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -86,6 +86,7 @@ public class DataManager {
private final Context mContext;
private final Injector mInjector;
private final ScheduledExecutorService mUsageStatsQueryExecutor;
+ private final ScheduledExecutorService mDiskReadWriterExecutor;
private final SparseArray<UserData> mUserDataArray = new SparseArray<>();
private final SparseArray<BroadcastReceiver> mBroadcastReceivers = new SparseArray<>();
@@ -113,6 +114,7 @@ public class DataManager {
BackgroundThread.getHandler());
mMmsSmsContentObserver = new MmsSmsContentObserver(
BackgroundThread.getHandler());
+ mDiskReadWriterExecutor = mInjector.createScheduledExecutor();
}
/** Initialization. Called when the system services are up running. */
@@ -122,13 +124,18 @@ public class DataManager {
mUserManager = mContext.getSystemService(UserManager.class);
mShortcutServiceInternal.addListener(new ShortcutServiceListener());
+
+ IntentFilter shutdownIntentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
+ BroadcastReceiver shutdownBroadcastReceiver = new ShutdownBroadcastReceiver();
+ mContext.registerReceiver(shutdownBroadcastReceiver, shutdownIntentFilter);
}
/** This method is called when a user is unlocked. */
public void onUserUnlocked(int userId) {
UserData userData = mUserDataArray.get(userId);
if (userData == null) {
- userData = new UserData(userId);
+ userData = new UserData(userId, mDiskReadWriterExecutor,
+ mInjector.createContactsQueryHelper(mContext));
mUserDataArray.put(userId, userData);
}
userData.setUserUnlocked();
@@ -662,6 +669,14 @@ public class DataManager {
}
}
+ private class ShutdownBroadcastReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ forAllPackages(PackageData::saveToDisk);
+ }
+ }
+
@VisibleForTesting
static class Injector {
diff --git a/services/people/java/com/android/server/people/data/PackageData.java b/services/people/java/com/android/server/people/data/PackageData.java
index 75b870c74591..f67699c28531 100644
--- a/services/people/java/com/android/server/people/data/PackageData.java
+++ b/services/people/java/com/android/server/people/data/PackageData.java
@@ -22,6 +22,8 @@ import android.annotation.UserIdInt;
import android.content.LocusId;
import android.text.TextUtils;
+import java.io.File;
+import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -43,17 +45,36 @@ public class PackageData {
private final Predicate<String> mIsDefaultSmsAppPredicate;
+ private final File mPackageDataDir;
+
PackageData(@NonNull String packageName, @UserIdInt int userId,
@NonNull Predicate<String> isDefaultDialerPredicate,
- @NonNull Predicate<String> isDefaultSmsAppPredicate) {
+ @NonNull Predicate<String> isDefaultSmsAppPredicate,
+ @NonNull ScheduledExecutorService scheduledExecutorService,
+ @NonNull File perUserPeopleDataDir,
+ @NonNull ContactsQueryHelper helper) {
mPackageName = packageName;
mUserId = userId;
- mConversationStore = new ConversationStore();
+
+ mPackageDataDir = new File(perUserPeopleDataDir, mPackageName);
+ mConversationStore = new ConversationStore(mPackageDataDir, scheduledExecutorService,
+ helper);
mEventStore = new EventStore();
mIsDefaultDialerPredicate = isDefaultDialerPredicate;
mIsDefaultSmsAppPredicate = isDefaultSmsAppPredicate;
}
+ /** Called when user is unlocked. */
+ void loadFromDisk() {
+ mPackageDataDir.mkdirs();
+ mConversationStore.loadConversationsFromDisk();
+ }
+
+ /** Called when device is shutting down. */
+ void saveToDisk() {
+ mConversationStore.saveConversationsToDisk();
+ }
+
@NonNull
public String getPackageName() {
return mPackageName;
diff --git a/services/people/java/com/android/server/people/data/UserData.java b/services/people/java/com/android/server/people/data/UserData.java
index 4e8fd16d05fd..aaa5db878e08 100644
--- a/services/people/java/com/android/server/people/data/UserData.java
+++ b/services/people/java/com/android/server/people/data/UserData.java
@@ -19,10 +19,13 @@ package com.android.server.people.data;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.os.Environment;
import android.text.TextUtils;
import android.util.ArrayMap;
+import java.io.File;
import java.util.Map;
+import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Consumer;
/** The data associated with a user profile. */
@@ -30,6 +33,12 @@ class UserData {
private final @UserIdInt int mUserId;
+ private final File mPerUserPeopleDataDir;
+
+ private final ScheduledExecutorService mScheduledExecutorService;
+
+ private final ContactsQueryHelper mHelper;
+
private boolean mIsUnlocked;
private Map<String, PackageData> mPackageDataMap = new ArrayMap<>();
@@ -40,8 +49,12 @@ class UserData {
@Nullable
private String mDefaultSmsApp;
- UserData(@UserIdInt int userId) {
+ UserData(@UserIdInt int userId, @NonNull ScheduledExecutorService scheduledExecutorService,
+ ContactsQueryHelper helper) {
mUserId = userId;
+ mPerUserPeopleDataDir = new File(Environment.getDataSystemCeDirectory(mUserId), "people");
+ mScheduledExecutorService = scheduledExecutorService;
+ mHelper = helper;
}
@UserIdInt int getUserId() {
@@ -56,6 +69,13 @@ class UserData {
void setUserUnlocked() {
mIsUnlocked = true;
+
+ // Ensures per user root directory for people data is present, and attempt to load
+ // data from disk.
+ mPerUserPeopleDataDir.mkdirs();
+ for (PackageData packageData : mPackageDataMap.values()) {
+ packageData.loadFromDisk();
+ }
}
void setUserStopped() {
@@ -103,7 +123,8 @@ class UserData {
}
private PackageData createPackageData(String packageName) {
- return new PackageData(packageName, mUserId, this::isDefaultDialer, this::isDefaultSmsApp);
+ return new PackageData(packageName, mUserId, this::isDefaultDialer, this::isDefaultSmsApp,
+ mScheduledExecutorService, mPerUserPeopleDataDir, mHelper);
}
private boolean isDefaultDialer(String packageName) {
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java b/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java
index 223a98b6c8f6..8daef5fad032 100644
--- a/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java
@@ -67,8 +67,7 @@ public class ShadowPerformUnifiedRestoreTask {
int pmToken,
boolean isFullSystemRestore,
@Nullable String[] filterSet,
- OnTaskFinishedListener listener,
- Map<String, Set<String>> excludedKeys) {
+ OnTaskFinishedListener listener) {
mBackupManagerService = backupManagerService;
mPackage = targetPackage;
mIsFullSystemRestore = isFullSystemRestore;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java
index fbb55fdeeb8f..5a96347c4ae1 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java
@@ -25,7 +25,9 @@ import static org.hamcrest.Matchers.emptyArray;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
+import static org.mockito.AdditionalAnswers.returnsFirstArg;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -133,6 +135,8 @@ public class AccessibilitySecurityPolicyTest {
mA11ySecurityPolicy = new AccessibilitySecurityPolicy(mMockContext, mMockA11yUserManager);
mA11ySecurityPolicy.setAccessibilityWindowManager(mMockA11yWindowManager);
mA11ySecurityPolicy.setAppWidgetManager(mMockAppWidgetManager);
+
+ when(mMockA11yWindowManager.resolveParentWindowIdLocked(anyInt())).then(returnsFirstArg());
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
index 9db5a080c093..10a86f9ea527 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
@@ -88,6 +88,10 @@ public class AccessibilityWindowManagerTest {
private static final int DEFAULT_FOCUSED_INDEX = 1;
private static final int SCREEN_WIDTH = 1080;
private static final int SCREEN_HEIGHT = 1920;
+ private static final int INVALID_ID = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+ private static final int HOST_WINDOW_ID = 10;
+ private static final int EMBEDDED_WINDOW_ID = 11;
+ private static final int OTHER_WINDOW_ID = 12;
private AccessibilityWindowManager mA11yWindowManager;
// Window manager will support multiple focused window if config_perDisplayFocusEnabled is true,
@@ -117,6 +121,10 @@ public class AccessibilityWindowManagerTest {
@Mock private AccessibilitySecurityPolicy mMockA11ySecurityPolicy;
@Mock private AccessibilitySecurityPolicy.AccessibilityUserManager mMockA11yUserManager;
+ @Mock private IBinder mMockHostToken;
+ @Mock private IBinder mMockEmbeddedToken;
+ @Mock private IBinder mMockInvalidToken;
+
@Before
public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
@@ -140,6 +148,8 @@ public class AccessibilityWindowManagerTest {
// AccessibilityEventSender is invoked during onWindowsForAccessibilityChanged.
// Resets it for mockito verify of further test case.
Mockito.reset(mMockA11yEventSender);
+
+ registerLeashedTokenAndWindowId();
}
@After
@@ -407,6 +417,51 @@ public class AccessibilityWindowManagerTest {
}
@Test
+ public void resolveParentWindowId_windowIsNotEmbedded_shouldReturnGivenId()
+ throws RemoteException {
+ final int windowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY, false,
+ Mockito.mock(IBinder.class), USER_SYSTEM_ID);
+ assertEquals(windowId, mA11yWindowManager.resolveParentWindowIdLocked(windowId));
+ }
+
+ @Test
+ public void resolveParentWindowId_windowIsNotRegistered_shouldReturnGivenId() {
+ final int windowId = -1;
+ assertEquals(windowId, mA11yWindowManager.resolveParentWindowIdLocked(windowId));
+ }
+
+ @Test
+ public void resolveParentWindowId_windowIsAssociated_shouldReturnParentWindowId()
+ throws RemoteException {
+ final IBinder mockHostToken = Mockito.mock(IBinder.class);
+ final IBinder mockEmbeddedToken = Mockito.mock(IBinder.class);
+ final int hostWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
+ false, mockHostToken, USER_SYSTEM_ID);
+ final int embeddedWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
+ false, mockEmbeddedToken, USER_SYSTEM_ID);
+ mA11yWindowManager.associateEmbeddedHierarchyLocked(mockHostToken, mockEmbeddedToken);
+ final int resolvedWindowId = mA11yWindowManager.resolveParentWindowIdLocked(
+ embeddedWindowId);
+ assertEquals(hostWindowId, resolvedWindowId);
+ }
+
+ @Test
+ public void resolveParentWindowId_windowIsDisassociated_shouldReturnGivenId()
+ throws RemoteException {
+ final IBinder mockHostToken = Mockito.mock(IBinder.class);
+ final IBinder mockEmbeddedToken = Mockito.mock(IBinder.class);
+ final int hostWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
+ false, mockHostToken, USER_SYSTEM_ID);
+ final int embeddedWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
+ false, mockEmbeddedToken, USER_SYSTEM_ID);
+ mA11yWindowManager.associateEmbeddedHierarchyLocked(mockHostToken, mockEmbeddedToken);
+ mA11yWindowManager.disassociateEmbeddedHierarchyLocked(mockEmbeddedToken);
+ final int resolvedWindowId = mA11yWindowManager.resolveParentWindowIdLocked(
+ embeddedWindowId);
+ assertEquals(embeddedWindowId, resolvedWindowId);
+ }
+
+ @Test
public void computePartialInteractiveRegionForWindow_wholeVisible_returnWholeRegion() {
// Updates top 2 z-order WindowInfo are whole visible.
WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
@@ -726,6 +781,64 @@ public class AccessibilityWindowManagerTest {
token.asBinder(), -1);
}
+ @Test
+ public void getHostTokenLocked_hierarchiesAreAssociated_shouldReturnHostToken() {
+ mA11yWindowManager.associateLocked(mMockEmbeddedToken, mMockHostToken);
+ final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockEmbeddedToken);
+ assertEquals(hostToken, mMockHostToken);
+ }
+
+ @Test
+ public void getHostTokenLocked_hierarchiesAreNotAssociated_shouldReturnNull() {
+ final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockEmbeddedToken);
+ assertNull(hostToken);
+ }
+
+ @Test
+ public void getHostTokenLocked_embeddedHierarchiesAreDisassociated_shouldReturnNull() {
+ mA11yWindowManager.associateLocked(mMockEmbeddedToken, mMockHostToken);
+ mA11yWindowManager.disassociateLocked(mMockEmbeddedToken);
+ final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockEmbeddedToken);
+ assertNull(hostToken);
+ }
+
+ @Test
+ public void getHostTokenLocked_hostHierarchiesAreDisassociated_shouldReturnNull() {
+ mA11yWindowManager.associateLocked(mMockEmbeddedToken, mMockHostToken);
+ mA11yWindowManager.disassociateLocked(mMockHostToken);
+ final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockHostToken);
+ assertNull(hostToken);
+ }
+
+ @Test
+ public void getWindowIdLocked_windowIsRegistered_shouldReturnWindowId() {
+ final int windowId = mA11yWindowManager.getWindowIdLocked(mMockHostToken);
+ assertEquals(windowId, HOST_WINDOW_ID);
+ }
+
+ @Test
+ public void getWindowIdLocked_windowIsNotRegistered_shouldReturnInvalidWindowId() {
+ final int windowId = mA11yWindowManager.getWindowIdLocked(mMockInvalidToken);
+ assertEquals(windowId, INVALID_ID);
+ }
+
+ @Test
+ public void getTokenLocked_windowIsRegistered_shouldReturnToken() {
+ final IBinder token = mA11yWindowManager.getTokenLocked(HOST_WINDOW_ID);
+ assertEquals(token, mMockHostToken);
+ }
+
+ @Test
+ public void getTokenLocked_windowIsNotRegistered_shouldReturnNull() {
+ final IBinder token = mA11yWindowManager.getTokenLocked(OTHER_WINDOW_ID);
+ assertNull(token);
+ }
+
+ private void registerLeashedTokenAndWindowId() {
+ mA11yWindowManager.registerIdLocked(mMockHostToken, HOST_WINDOW_ID);
+ mA11yWindowManager.registerIdLocked(mMockEmbeddedToken, EMBEDDED_WINDOW_ID);
+ }
+
private void startTrackingPerDisplay(int displayId) throws RemoteException {
ArrayList<WindowInfo> windowInfosForDisplay = new ArrayList<>();
// Adds RemoteAccessibilityConnection into AccessibilityWindowManager, and copy
@@ -784,6 +897,7 @@ public class AccessibilityWindowManagerTest {
IAccessibilityInteractionConnection.class);
final IBinder mockConnectionBinder = Mockito.mock(IBinder.class);
final IBinder mockWindowBinder = Mockito.mock(IBinder.class);
+ final IBinder mockLeashToken = Mockito.mock(IBinder.class);
when(mockA11yConnection.asBinder()).thenReturn(mockConnectionBinder);
when(mockWindowToken.asBinder()).thenReturn(mockWindowBinder);
when(mMockA11ySecurityPolicy.isCallerInteractingAcrossUsers(userId))
@@ -792,11 +906,31 @@ public class AccessibilityWindowManagerTest {
.thenReturn(displayId);
int windowId = mA11yWindowManager.addAccessibilityInteractionConnection(
- mockWindowToken, mockA11yConnection, PACKAGE_NAME, userId);
+ mockWindowToken, mockLeashToken, mockA11yConnection, PACKAGE_NAME, userId);
mA11yWindowTokens.put(windowId, mockWindowToken);
return mockWindowToken;
}
+ private int addAccessibilityInteractionConnection(int displayId, boolean bGlobal,
+ IBinder leashToken, int userId) throws RemoteException {
+ final IWindow mockWindowToken = Mockito.mock(IWindow.class);
+ final IAccessibilityInteractionConnection mockA11yConnection = Mockito.mock(
+ IAccessibilityInteractionConnection.class);
+ final IBinder mockConnectionBinder = Mockito.mock(IBinder.class);
+ final IBinder mockWindowBinder = Mockito.mock(IBinder.class);
+ when(mockA11yConnection.asBinder()).thenReturn(mockConnectionBinder);
+ when(mockWindowToken.asBinder()).thenReturn(mockWindowBinder);
+ when(mMockA11ySecurityPolicy.isCallerInteractingAcrossUsers(userId))
+ .thenReturn(bGlobal);
+ when(mMockWindowManagerInternal.getDisplayIdForWindow(mockWindowToken.asBinder()))
+ .thenReturn(displayId);
+
+ int windowId = mA11yWindowManager.addAccessibilityInteractionConnection(
+ mockWindowToken, leashToken, mockA11yConnection, PACKAGE_NAME, userId);
+ mA11yWindowTokens.put(windowId, mockWindowToken);
+ return windowId;
+ }
+
private void addWindowInfo(ArrayList<WindowInfo> windowInfos, IWindow windowToken, int layer) {
final WindowInfo windowInfo = WindowInfo.obtain();
windowInfo.type = AccessibilityWindowInfo.TYPE_APPLICATION;
diff --git a/services/tests/servicestests/src/com/android/server/backup/UserBackupPreferencesTest.java b/services/tests/servicestests/src/com/android/server/backup/UserBackupPreferencesTest.java
index d6efe35723db..5800acabe8a5 100644
--- a/services/tests/servicestests/src/com/android/server/backup/UserBackupPreferencesTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/UserBackupPreferencesTest.java
@@ -16,8 +16,6 @@
package com.android.server.backup;
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import androidx.test.InstrumentationRegistry;
@@ -33,18 +31,15 @@ import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
import java.util.Arrays;
-import java.util.HashSet;
import java.util.List;
-import java.util.Map;
import java.util.Set;
@Presubmit
@RunWith(AndroidJUnit4.class)
public class UserBackupPreferencesTest {
private static final String EXCLUDED_PACKAGE_1 = "package1";
- private static final String EXCLUDED_PACKAGE_2 = "package2";
private static final List<String> EXCLUDED_KEYS_1 = Arrays.asList("key1", "key2");
- private static final List<String> EXCLUDED_KEYS_2 = Arrays.asList("key1");
+ private static final List<String> EXCLUDED_KEYS_2 = Arrays.asList("key3");
@Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
@@ -60,27 +55,13 @@ public class UserBackupPreferencesTest {
}
@Test
- public void testGetExcludedKeysForPackages_returnsExcludedKeys() {
+ public void testGetExcludedKeysForPackage_returnsExcludedKeys() {
mExcludedRestoreKeysStorage.addExcludedKeys(EXCLUDED_PACKAGE_1, EXCLUDED_KEYS_1);
- mExcludedRestoreKeysStorage.addExcludedKeys(EXCLUDED_PACKAGE_2, EXCLUDED_KEYS_2);
+ mExcludedRestoreKeysStorage.addExcludedKeys(EXCLUDED_PACKAGE_1, EXCLUDED_KEYS_2);
- Map<String, Set<String>> excludedKeys =
- mExcludedRestoreKeysStorage.getExcludedRestoreKeysForPackages(EXCLUDED_PACKAGE_1);
- assertTrue(excludedKeys.containsKey(EXCLUDED_PACKAGE_1));
- assertFalse(excludedKeys.containsKey(EXCLUDED_PACKAGE_2));
- assertEquals(new HashSet<>(EXCLUDED_KEYS_1), excludedKeys.get(EXCLUDED_PACKAGE_1));
- }
-
- @Test
- public void testGetExcludedKeysForPackages_withEmpty_list_returnsAllExcludedKeys() {
- mExcludedRestoreKeysStorage.addExcludedKeys(EXCLUDED_PACKAGE_1, EXCLUDED_KEYS_1);
- mExcludedRestoreKeysStorage.addExcludedKeys(EXCLUDED_PACKAGE_2, EXCLUDED_KEYS_2);
-
- Map<String, Set<String>> excludedKeys =
- mExcludedRestoreKeysStorage.getAllExcludedRestoreKeys();
- assertTrue(excludedKeys.containsKey(EXCLUDED_PACKAGE_1));
- assertTrue(excludedKeys.containsKey(EXCLUDED_PACKAGE_2));
- assertEquals(new HashSet<>(EXCLUDED_KEYS_1), excludedKeys.get(EXCLUDED_PACKAGE_1));
- assertEquals(new HashSet<>(EXCLUDED_KEYS_2), excludedKeys.get(EXCLUDED_PACKAGE_2));
+ Set<String> excludedKeys =
+ mExcludedRestoreKeysStorage.getExcludedRestoreKeysForPackage(EXCLUDED_PACKAGE_1);
+ assertTrue(excludedKeys.containsAll(EXCLUDED_KEYS_1));
+ assertTrue(excludedKeys.containsAll(EXCLUDED_KEYS_2));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java b/services/tests/servicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
index 3d220432cc8e..017c93975286 100644
--- a/services/tests/servicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
@@ -20,7 +20,9 @@ import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import androidx.test.InstrumentationRegistry;
@@ -30,6 +32,8 @@ import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
import android.platform.test.annotations.Presubmit;
+import com.android.server.backup.UserBackupManagerService;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -40,6 +44,7 @@ import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.ArrayDeque;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -59,6 +64,7 @@ public class PerformUnifiedRestoreTaskTest {
@Mock private BackupDataInput mBackupDataInput;
@Mock private BackupDataOutput mBackupDataOutput;
+ @Mock private UserBackupManagerService mBackupManagerService;
private Set<String> mExcludedkeys = new HashSet<>();
private Map<String, String> mBackupData = new HashMap<>();
@@ -99,6 +105,8 @@ public class PerformUnifiedRestoreTaskTest {
return null;
}
});
+
+ mRestoreTask = new PerformUnifiedRestoreTask(mBackupManagerService);
}
private void populateTestData() {
@@ -114,8 +122,9 @@ public class PerformUnifiedRestoreTaskTest {
@Test
public void testFilterExcludedKeys() throws Exception {
- mRestoreTask = new PerformUnifiedRestoreTask(Collections.singletonMap(
- PACKAGE_NAME, mExcludedkeys));
+ when(mBackupManagerService.getExcludedRestoreKeys(eq(PACKAGE_NAME))).thenReturn(
+ mExcludedkeys);
+
mRestoreTask.filterExcludedKeys(PACKAGE_NAME, mBackupDataInput, mBackupDataOutput);
// Verify only the correct were written into BackupDataOutput object.
@@ -125,32 +134,49 @@ public class PerformUnifiedRestoreTaskTest {
}
@Test
+ public void testGetExcludedKeysForPackage_alwaysReturnsLatestKeys() {
+ Set<String> firstExcludedKeys = new HashSet<>(Collections.singletonList(EXCLUDED_KEY_1));
+ when(mBackupManagerService.getExcludedRestoreKeys(eq(PACKAGE_NAME))).thenReturn(
+ firstExcludedKeys);
+ assertEquals(firstExcludedKeys, mRestoreTask.getExcludedKeysForPackage(PACKAGE_NAME));
+
+
+ Set<String> secondExcludedKeys = new HashSet<>(Arrays.asList(EXCLUDED_KEY_1,
+ EXCLUDED_KEY_2));
+ when(mBackupManagerService.getExcludedRestoreKeys(eq(PACKAGE_NAME))).thenReturn(
+ secondExcludedKeys);
+ assertEquals(secondExcludedKeys, mRestoreTask.getExcludedKeysForPackage(PACKAGE_NAME));
+ }
+
+ @Test
public void testStageBackupData_stageForNonSystemPackageWithKeysToExclude() {
- mRestoreTask = new PerformUnifiedRestoreTask(Collections.singletonMap(
- PACKAGE_NAME, mExcludedkeys));
+ when(mBackupManagerService.getExcludedRestoreKeys(eq(NON_SYSTEM_PACKAGE_NAME))).thenReturn(
+ mExcludedkeys);
assertTrue(mRestoreTask.shouldStageBackupData(NON_SYSTEM_PACKAGE_NAME));
}
@Test
public void testStageBackupData_stageForNonSystemPackageWithNoKeysToExclude() {
- mRestoreTask = new PerformUnifiedRestoreTask(Collections.emptyMap());
+ when(mBackupManagerService.getExcludedRestoreKeys(any())).thenReturn(
+ Collections.emptySet());
assertTrue(mRestoreTask.shouldStageBackupData(NON_SYSTEM_PACKAGE_NAME));
}
@Test
public void testStageBackupData_doNotStageForSystemPackageWithNoKeysToExclude() {
- mRestoreTask = new PerformUnifiedRestoreTask(Collections.emptyMap());
+ when(mBackupManagerService.getExcludedRestoreKeys(any())).thenReturn(
+ Collections.emptySet());
assertFalse(mRestoreTask.shouldStageBackupData(SYSTEM_PACKAGE_NAME));
}
@Test
public void testStageBackupData_stageForSystemPackageWithKeysToExclude() {
- mRestoreTask = new PerformUnifiedRestoreTask(Collections.singletonMap(
- PACKAGE_NAME, mExcludedkeys));
+ when(mBackupManagerService.getExcludedRestoreKeys(eq(SYSTEM_PACKAGE_NAME))).thenReturn(
+ mExcludedkeys);
- assertTrue(mRestoreTask.shouldStageBackupData(NON_SYSTEM_PACKAGE_NAME));
+ assertTrue(mRestoreTask.shouldStageBackupData(SYSTEM_PACKAGE_NAME));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 156cd6e5826d..e5adb80e6ef9 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -283,7 +283,7 @@ public class BiometricServiceTest {
null /* authenticators */);
waitForIdle();
verify(mReceiver1).onError(
- eq(BiometricAuthenticator.TYPE_NONE),
+ eq(BiometricAuthenticator.TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE),
eq(0 /* vendorCode */));
}
@@ -1117,14 +1117,14 @@ public class BiometricServiceTest {
// STRONG-only auth is not available
int authenticators = Authenticators.BIOMETRIC_STRONG;
- assertEquals(BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE,
+ assertEquals(BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED,
invokeCanAuthenticate(mBiometricService, authenticators));
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
authenticators);
waitForIdle();
verify(mReceiver1).onError(
- eq(BiometricAuthenticator.TYPE_NONE),
- eq(BiometricPrompt.BIOMETRIC_ERROR_HW_NOT_PRESENT),
+ eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+ eq(BiometricPrompt.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED),
eq(0) /* vendorCode */);
// Request for weak auth works
@@ -1154,7 +1154,7 @@ public class BiometricServiceTest {
false /* requireConfirmation */,
authenticators);
waitForIdle();
- assertTrue(Utils.isDeviceCredentialAllowed(mBiometricService.mCurrentAuthSession.mBundle));
+ assertTrue(Utils.isCredentialRequested(mBiometricService.mCurrentAuthSession.mBundle));
verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
eq(mBiometricService.mCurrentAuthSession.mBundle),
any(IBiometricServiceReceiverInternal.class),
@@ -1162,6 +1162,28 @@ public class BiometricServiceTest {
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
eq(TEST_PACKAGE_NAME));
+
+ // Un-downgrading the authenticator allows successful strong auth
+ for (BiometricService.AuthenticatorWrapper wrapper : mBiometricService.mAuthenticators) {
+ if (wrapper.id == testId) {
+ wrapper.updateStrength(Authenticators.BIOMETRIC_STRONG);
+ }
+ }
+
+ resetReceiver();
+ authenticators = Authenticators.BIOMETRIC_STRONG;
+ assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+ invokeCanAuthenticate(mBiometricService, authenticators));
+ invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */, authenticators);
+ waitForIdle();
+ verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
+ eq(mBiometricService.mCurrentAuthSession.mBundle),
+ any(IBiometricServiceReceiverInternal.class),
+ eq(BiometricAuthenticator.TYPE_FINGERPRINT /* biometricModality */),
+ anyBoolean() /* requireConfirmation */,
+ anyInt() /* userId */,
+ eq(TEST_PACKAGE_NAME));
}
@Test(expected = IllegalStateException.class)
@@ -1245,6 +1267,19 @@ public class BiometricServiceTest {
}
@Test
+ public void testAuthentication_normalAppIgnoresDevicePolicy() throws Exception {
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ when(mDevicePolicyManager
+ .getKeyguardDisabledFeatures(any() /* admin */, anyInt() /* userHandle */))
+ .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
+ invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */, Authenticators.BIOMETRIC_STRONG);
+ waitForIdle();
+ assertEquals(mBiometricService.mCurrentAuthSession.mState,
+ BiometricService.STATE_AUTH_STARTED);
+ }
+
+ @Test
public void testWorkAuthentication_faceWorksIfNotDisabledByDevicePolicyManager()
throws Exception {
setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
index 312ff2ca84a1..df242f3efc16 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
@@ -91,31 +91,31 @@ public class UtilsTest {
@Test
public void testIsDeviceCredentialAllowed_withIntegerFlags() {
int authenticators = 0;
- assertFalse(Utils.isDeviceCredentialAllowed(authenticators));
+ assertFalse(Utils.isCredentialRequested(authenticators));
authenticators |= Authenticators.DEVICE_CREDENTIAL;
- assertTrue(Utils.isDeviceCredentialAllowed(authenticators));
+ assertTrue(Utils.isCredentialRequested(authenticators));
authenticators |= Authenticators.BIOMETRIC_WEAK;
- assertTrue(Utils.isDeviceCredentialAllowed(authenticators));
+ assertTrue(Utils.isCredentialRequested(authenticators));
}
@Test
public void testIsDeviceCredentialAllowed_withBundle() {
Bundle bundle = new Bundle();
- assertFalse(Utils.isDeviceCredentialAllowed(bundle));
+ assertFalse(Utils.isCredentialRequested(bundle));
int authenticators = 0;
bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
- assertFalse(Utils.isDeviceCredentialAllowed(bundle));
+ assertFalse(Utils.isCredentialRequested(bundle));
authenticators |= Authenticators.DEVICE_CREDENTIAL;
bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
- assertTrue(Utils.isDeviceCredentialAllowed(bundle));
+ assertTrue(Utils.isCredentialRequested(bundle));
authenticators |= Authenticators.BIOMETRIC_WEAK;
bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
- assertTrue(Utils.isDeviceCredentialAllowed(bundle));
+ assertTrue(Utils.isCredentialRequested(bundle));
}
@Test
@@ -140,14 +140,14 @@ public class UtilsTest {
for (int i = 0; i <= 7; i++) {
int authenticators = 1 << i;
bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
- assertTrue(Utils.isBiometricAllowed(bundle));
+ assertTrue(Utils.isBiometricRequested(bundle));
}
// The rest of the bits are not allowed to integrate with the public APIs
for (int i = 8; i < 32; i++) {
int authenticators = 1 << i;
bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
- assertFalse(Utils.isBiometricAllowed(bundle));
+ assertFalse(Utils.isBiometricRequested(bundle));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
index d40130a62fd9..8dae48cafd7b 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
@@ -211,7 +211,8 @@ public class AppIntegrityManagerServiceImplTest {
IntentSender mockReceiver = mock(IntentSender.class);
List<Rule> rules =
Arrays.asList(
- new Rule(IntegrityFormula.PACKAGE_NAME.equalTo(PACKAGE_NAME), Rule.DENY));
+ new Rule(IntegrityFormula.Application.packageNameEquals(PACKAGE_NAME),
+ Rule.DENY));
mService.updateRuleSet(VERSION, new ParceledListSlice<>(rules), mockReceiver);
runJobInHandler();
@@ -230,7 +231,8 @@ public class AppIntegrityManagerServiceImplTest {
IntentSender mockReceiver = mock(IntentSender.class);
List<Rule> rules =
Arrays.asList(
- new Rule(IntegrityFormula.PACKAGE_NAME.equalTo(PACKAGE_NAME), Rule.DENY));
+ new Rule(IntegrityFormula.Application.packageNameEquals(PACKAGE_NAME),
+ Rule.DENY));
mService.updateRuleSet(VERSION, new ParceledListSlice<>(rules), mockReceiver);
runJobInHandler();
@@ -390,7 +392,7 @@ public class AppIntegrityManagerServiceImplTest {
public void getCurrentRules() throws Exception {
whitelistUsAsRuleProvider();
makeUsSystemApp();
- Rule rule = new Rule(IntegrityFormula.PACKAGE_NAME.equalTo("package"), Rule.DENY);
+ Rule rule = new Rule(IntegrityFormula.Application.packageNameEquals("package"), Rule.DENY);
when(mIntegrityFileManager.readRules(any())).thenReturn(Arrays.asList(rule));
assertThat(mService.getCurrentRules().getList()).containsExactly(rule);
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java
index 38cf562f8c5b..3dc26afdb9af 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java
@@ -58,7 +58,7 @@ public class RuleBinaryParserTest {
getBits(COMPOUND_FORMULA_END, SEPARATOR_BITS);
private static final String ATOMIC_FORMULA_START_BITS =
getBits(ATOMIC_FORMULA_START, SEPARATOR_BITS);
- private static final int INVALID_FORMULA_SEPARATOR_VALUE = 3;
+ private static final int INVALID_FORMULA_SEPARATOR_VALUE = (1 << SEPARATOR_BITS) - 1;
private static final String INVALID_FORMULA_SEPARATOR_BITS =
getBits(INVALID_FORMULA_SEPARATOR_VALUE, SEPARATOR_BITS);
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
index 913aff7daaa9..ea9e6ff86728 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
@@ -112,6 +112,7 @@ public class RuleIndexingDetailsIdentifierTest {
ATOMIC_FORMULA_WITH_VERSION_CODE,
ATOMIC_FORMULA_WITH_ISPREINSTALLED)),
Rule.DENY);
+ public static final int INVALID_FORMULA_TAG = -1;
@Test
public void getIndexType_nullRule() {
@@ -290,7 +291,7 @@ public class RuleIndexingDetailsIdentifierTest {
return new AtomicFormula(0) {
@Override
public int getTag() {
- return 4;
+ return INVALID_FORMULA_TAG;
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/location/gnss/GnssManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/location/gnss/GnssManagerServiceTest.java
index f16cf35bfb34..4ba584e67929 100644
--- a/services/tests/servicestests/src/com/android/server/location/gnss/GnssManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/gnss/GnssManagerServiceTest.java
@@ -39,6 +39,7 @@ import android.location.GnssClock;
import android.location.GnssMeasurementCorrections;
import android.location.GnssMeasurementsEvent;
import android.location.GnssNavigationMessage;
+import android.location.GnssRequest;
import android.location.GnssSingleSatCorrection;
import android.location.IBatchedLocationCallback;
import android.location.IGnssMeasurementsListener;
@@ -563,7 +564,7 @@ public class GnssManagerServiceTest {
assertThrows(SecurityException.class,
() -> mGnssManagerService.addGnssMeasurementsListener(
- mockGnssMeasurementsListener,
+ new GnssRequest.Builder().build(), mockGnssMeasurementsListener,
"com.android.server", "abcd123", "TestGnssMeasurementsListener"));
mTestGnssMeasurementsProvider.onMeasurementsAvailable(gnssMeasurementsEvent);
@@ -580,8 +581,11 @@ public class GnssManagerServiceTest {
enableLocationPermissions();
- assertThat(mGnssManagerService.addGnssMeasurementsListener(mockGnssMeasurementsListener,
- "com.android.server", "abcd123", "TestGnssMeasurementsListener")).isEqualTo(true);
+ assertThat(mGnssManagerService.addGnssMeasurementsListener(
+ new GnssRequest.Builder().build(),
+ mockGnssMeasurementsListener,
+ "com.android.server", "abcd123",
+ "TestGnssMeasurementsListener")).isEqualTo(true);
mTestGnssMeasurementsProvider.onMeasurementsAvailable(gnssMeasurementsEvent);
verify(mockGnssMeasurementsListener, times(1)).onGnssMeasurementsReceived(
@@ -626,8 +630,10 @@ public class GnssManagerServiceTest {
enableLocationPermissions();
- mGnssManagerService.addGnssMeasurementsListener(mockGnssMeasurementsListener,
- "com.android.server", "abcd123", "TestGnssMeasurementsListener");
+ mGnssManagerService.addGnssMeasurementsListener(new GnssRequest.Builder().build(),
+ mockGnssMeasurementsListener,
+ "com.android.server", "abcd123",
+ "TestGnssMeasurementsListener");
disableLocationPermissions();
@@ -648,8 +654,10 @@ public class GnssManagerServiceTest {
enableLocationPermissions();
- mGnssManagerService.addGnssMeasurementsListener(mockGnssMeasurementsListener,
- "com.android.server", "abcd123", "TestGnssMeasurementsListener");
+ mGnssManagerService.addGnssMeasurementsListener(new GnssRequest.Builder().build(),
+ mockGnssMeasurementsListener,
+ "com.android.server", "abcd123",
+ "TestGnssMeasurementsListener");
disableLocationPermissions();
diff --git a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
index 4ae374abb7c2..9213e1fe5a25 100644
--- a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
@@ -51,6 +51,7 @@ public final class PeopleServiceTest {
private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
private static final int APP_PREDICTION_TARGET_COUNT = 4;
private static final String TEST_PACKAGE_NAME = "com.example";
+ private static final int USER_ID = 0;
private PeopleServiceInternal mServiceInternal;
private PeopleService.LocalService mLocalService;
@@ -73,7 +74,7 @@ public final class PeopleServiceTest {
mServiceInternal = LocalServices.getService(PeopleServiceInternal.class);
mLocalService = (PeopleService.LocalService) mServiceInternal;
- mSessionId = new AppPredictionSessionId("abc");
+ mSessionId = new AppPredictionSessionId("abc", USER_ID);
mPredictionContext = new AppPredictionContext.Builder(mContext)
.setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE)
.setPredictedTargetCount(APP_PREDICTION_TARGET_COUNT)
diff --git a/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java b/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java
index 331ad5972fc1..03b5e38cadb7 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java
@@ -21,16 +21,24 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import android.annotation.Nullable;
+import android.content.Context;
import android.content.LocusId;
import android.content.pm.ShortcutInfo;
import android.net.Uri;
+import android.os.FileUtils;
+import android.text.format.DateUtils;
import android.util.ArraySet;
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.io.File;
import java.util.Set;
@RunWith(JUnit4.class)
@@ -42,11 +50,34 @@ public final class ConversationStoreTest {
private static final Uri CONTACT_URI = Uri.parse("tel:+1234567890");
private static final String PHONE_NUMBER = "+1234567890";
+ private static final String SHORTCUT_ID_2 = "ghi";
+ private static final String NOTIFICATION_CHANNEL_ID_2 = "test : ghi";
+ private static final LocusId LOCUS_ID_2 = new LocusId("jkl");
+ private static final Uri CONTACT_URI_2 = Uri.parse("tel:+3234567890");
+ private static final String PHONE_NUMBER_2 = "+3234567890";
+
+ private static final String SHORTCUT_ID_3 = "mno";
+ private static final String NOTIFICATION_CHANNEL_ID_3 = "test : mno";
+ private static final LocusId LOCUS_ID_3 = new LocusId("pqr");
+ private static final Uri CONTACT_URI_3 = Uri.parse("tel:+9234567890");
+ private static final String PHONE_NUMBER_3 = "+9234567890";
+
+ private MockScheduledExecutorService mMockScheduledExecutorService;
+ private TestContactQueryHelper mTestContactQueryHelper;
private ConversationStore mConversationStore;
+ private File mFile;
@Before
public void setUp() {
- mConversationStore = new ConversationStore();
+ Context ctx = InstrumentationRegistry.getContext();
+ mFile = new File(ctx.getCacheDir(), "testdir");
+ mTestContactQueryHelper = new TestContactQueryHelper(ctx);
+ resetConversationStore();
+ }
+
+ @After
+ public void tearDown() {
+ FileUtils.deleteContentsAndDir(mFile);
}
@Test
@@ -153,6 +184,130 @@ public final class ConversationStoreTest {
mConversationStore.getConversationByNotificationChannelId(NOTIFICATION_CHANNEL_ID));
}
+ @Test
+ public void testDataPersistenceAndRestoration() {
+ // Add conversation infos, causing it to be loaded to disk.
+ ConversationInfo in1 = buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI,
+ PHONE_NUMBER, NOTIFICATION_CHANNEL_ID);
+ ConversationInfo in2 = buildConversationInfo(SHORTCUT_ID_2, LOCUS_ID_2, CONTACT_URI_2,
+ PHONE_NUMBER_2, NOTIFICATION_CHANNEL_ID_2);
+ ConversationInfo in3 = buildConversationInfo(SHORTCUT_ID_3, LOCUS_ID_3, CONTACT_URI_3,
+ PHONE_NUMBER_3, NOTIFICATION_CHANNEL_ID_3);
+ mConversationStore.addOrUpdate(in1);
+ mConversationStore.addOrUpdate(in2);
+ mConversationStore.addOrUpdate(in3);
+
+ long futuresExecuted = mMockScheduledExecutorService.fastForwardTime(
+ 3L * DateUtils.MINUTE_IN_MILLIS);
+ assertEquals(1, futuresExecuted);
+
+ mMockScheduledExecutorService.resetTimeElapsedMillis();
+
+ // During restoration, we want to confirm that this conversation was removed.
+ mConversationStore.deleteConversation(SHORTCUT_ID_3);
+ mMockScheduledExecutorService.fastForwardTime(3L * DateUtils.MINUTE_IN_MILLIS);
+
+ mTestContactQueryHelper.setQueryResult(true, true);
+ mTestContactQueryHelper.setPhoneNumberResult(PHONE_NUMBER, PHONE_NUMBER_2);
+
+ resetConversationStore();
+ ConversationInfo out1 = mConversationStore.getConversation(SHORTCUT_ID);
+ ConversationInfo out2 = mConversationStore.getConversation(SHORTCUT_ID_2);
+ ConversationInfo out3 = mConversationStore.getConversation(SHORTCUT_ID_3);
+ mConversationStore.deleteConversation(SHORTCUT_ID);
+ mConversationStore.deleteConversation(SHORTCUT_ID_2);
+ mConversationStore.deleteConversation(SHORTCUT_ID_3);
+ assertEquals(in1, out1);
+ assertEquals(in2, out2);
+ assertNull(out3);
+ }
+
+ @Test
+ public void testDelayedDiskWrites() {
+ ConversationInfo in1 = buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI,
+ PHONE_NUMBER, NOTIFICATION_CHANNEL_ID);
+ ConversationInfo in2 = buildConversationInfo(SHORTCUT_ID_2, LOCUS_ID_2, CONTACT_URI_2,
+ PHONE_NUMBER_2, NOTIFICATION_CHANNEL_ID_2);
+ ConversationInfo in3 = buildConversationInfo(SHORTCUT_ID_3, LOCUS_ID_3, CONTACT_URI_3,
+ PHONE_NUMBER_3, NOTIFICATION_CHANNEL_ID_3);
+
+ mConversationStore.addOrUpdate(in1);
+ mMockScheduledExecutorService.fastForwardTime(3L * DateUtils.MINUTE_IN_MILLIS);
+ mMockScheduledExecutorService.resetTimeElapsedMillis();
+
+ // Should not see second conversation on disk because of disk write delay has not been
+ // reached.
+ mConversationStore.addOrUpdate(in2);
+ mMockScheduledExecutorService.fastForwardTime(DateUtils.MINUTE_IN_MILLIS);
+
+ mTestContactQueryHelper.setQueryResult(true);
+ mTestContactQueryHelper.setPhoneNumberResult(PHONE_NUMBER);
+
+ resetConversationStore();
+ ConversationInfo out1 = mConversationStore.getConversation(SHORTCUT_ID);
+ ConversationInfo out2 = mConversationStore.getConversation(SHORTCUT_ID_2);
+ assertEquals(in1, out1);
+ assertNull(out2);
+
+ mConversationStore.addOrUpdate(in2);
+ mMockScheduledExecutorService.fastForwardTime(3L * DateUtils.MINUTE_IN_MILLIS);
+ mMockScheduledExecutorService.resetTimeElapsedMillis();
+
+ mConversationStore.addOrUpdate(in3);
+ mMockScheduledExecutorService.fastForwardTime(3L * DateUtils.MINUTE_IN_MILLIS);
+
+ mTestContactQueryHelper.reset();
+ mTestContactQueryHelper.setQueryResult(true, true, true);
+ mTestContactQueryHelper.setPhoneNumberResult(PHONE_NUMBER, PHONE_NUMBER_2, PHONE_NUMBER_3);
+
+ resetConversationStore();
+ out1 = mConversationStore.getConversation(SHORTCUT_ID);
+ out2 = mConversationStore.getConversation(SHORTCUT_ID_2);
+ ConversationInfo out3 = mConversationStore.getConversation(SHORTCUT_ID_3);
+ assertEquals(in1, out1);
+ assertEquals(in2, out2);
+ assertEquals(in3, out3);
+ }
+
+ @Test
+ public void testMimicDevicePowerOff() {
+
+ // Even without fast forwarding time with our mock ScheduledExecutorService, we should
+ // see the conversations immediately saved to disk.
+ ConversationInfo in1 = buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI,
+ PHONE_NUMBER, NOTIFICATION_CHANNEL_ID);
+ ConversationInfo in2 = buildConversationInfo(SHORTCUT_ID_2, LOCUS_ID_2, CONTACT_URI_2,
+ PHONE_NUMBER_2, NOTIFICATION_CHANNEL_ID_2);
+
+ mConversationStore.addOrUpdate(in1);
+ mConversationStore.addOrUpdate(in2);
+ mConversationStore.saveConversationsToDisk();
+
+ // Ensure that futures were cancelled and the immediate flush occurred.
+ assertEquals(0, mMockScheduledExecutorService.getFutures().size());
+
+ // Expect to see 2 executes: loadConversationFromDisk and saveConversationsToDisk.
+ // loadConversationFromDisk gets called each time we call #resetConversationStore().
+ assertEquals(2, mMockScheduledExecutorService.getExecutes().size());
+
+ mTestContactQueryHelper.setQueryResult(true, true);
+ mTestContactQueryHelper.setPhoneNumberResult(PHONE_NUMBER, PHONE_NUMBER_2);
+
+ resetConversationStore();
+ ConversationInfo out1 = mConversationStore.getConversation(SHORTCUT_ID);
+ ConversationInfo out2 = mConversationStore.getConversation(SHORTCUT_ID_2);
+ assertEquals(in1, out1);
+ assertEquals(in2, out2);
+ }
+
+ private void resetConversationStore() {
+ mFile.mkdir();
+ mMockScheduledExecutorService = new MockScheduledExecutorService();
+ mConversationStore = new ConversationStore(mFile, mMockScheduledExecutorService,
+ mTestContactQueryHelper);
+ mConversationStore.loadConversationsFromDisk();
+ }
+
private static ConversationInfo buildConversationInfo(String shortcutId) {
return buildConversationInfo(shortcutId, null, null, null, null);
}
@@ -171,4 +326,54 @@ public final class ConversationStoreTest {
.setBubbled(true)
.build();
}
+
+ private static class TestContactQueryHelper extends ContactsQueryHelper {
+
+ private int mQueryCalls;
+ private boolean[] mQueryResult;
+
+ private int mPhoneNumberCalls;
+ private String[] mPhoneNumberResult;
+
+ TestContactQueryHelper(Context context) {
+ super(context);
+
+ mQueryCalls = 0;
+ mPhoneNumberCalls = 0;
+ }
+
+ private void setQueryResult(boolean... values) {
+ mQueryResult = values;
+ }
+
+ private void setPhoneNumberResult(String... values) {
+ mPhoneNumberResult = values;
+ }
+
+ private void reset() {
+ mQueryCalls = 0;
+ mQueryResult = null;
+ mPhoneNumberCalls = 0;
+ mPhoneNumberResult = null;
+ }
+
+ @Override
+ boolean query(String contactUri) {
+ if (mQueryResult != null && mQueryCalls < mQueryResult.length) {
+ return mQueryResult[mQueryCalls++];
+ }
+ mQueryCalls++;
+ return false;
+ }
+
+ @Override
+ @Nullable
+ String getPhoneNumber() {
+ if (mPhoneNumberResult != null && mPhoneNumberCalls < mPhoneNumberResult.length) {
+ return mPhoneNumberResult[mPhoneNumberCalls++];
+ }
+ mPhoneNumberCalls++;
+ return null;
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/people/data/MockScheduledExecutorService.java b/services/tests/servicestests/src/com/android/server/people/data/MockScheduledExecutorService.java
new file mode 100644
index 000000000000..8b8ba1247a4b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/people/data/MockScheduledExecutorService.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.people.data;
+
+import com.android.internal.util.Preconditions;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Delayed;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Mock implementation of ScheduledExecutorService for testing. All commands will run
+ * synchronously. Commands passed to {@link #submit(Runnable)} and {@link #execute(Runnable)} will
+ * run immediately. Commands scheduled via {@link #schedule(Runnable, long, TimeUnit)} will run
+ * after calling {@link #fastForwardTime(long)}.
+ */
+class MockScheduledExecutorService implements ScheduledExecutorService {
+
+ private final List<Runnable> mExecutes = new ArrayList<>();
+ private final List<MockScheduledFuture<?>> mFutures = new ArrayList<>();
+ private long mTimeElapsedMillis = 0;
+
+ /**
+ * Advances fake time, runs all the commands for which the delay has expired.
+ */
+ long fastForwardTime(long millis) {
+ mTimeElapsedMillis += millis;
+ ImmutableList<MockScheduledFuture<?>> futuresCopy = ImmutableList.copyOf(mFutures);
+ mFutures.clear();
+ long totalExecuted = 0;
+ for (MockScheduledFuture<?> future : futuresCopy) {
+ if (future.getDelay() < mTimeElapsedMillis) {
+ future.getCommand().run();
+ mExecutes.add(future.getCommand());
+ totalExecuted += 1;
+ } else {
+ mFutures.add(future);
+ }
+ }
+ return totalExecuted;
+ }
+
+ List<Runnable> getExecutes() {
+ return mExecutes;
+ }
+
+ List<MockScheduledFuture<?>> getFutures() {
+ return mFutures;
+ }
+
+ void resetTimeElapsedMillis() {
+ mTimeElapsedMillis = 0;
+ }
+
+ /**
+ * Fakes a schedule execution of {@link Runnable}. The command will be executed by an explicit
+ * call to {@link #fastForwardTime(long)}.
+ */
+ @Override
+ public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
+ Preconditions.checkState(unit == TimeUnit.MILLISECONDS);
+ MockScheduledFuture<?> future = new MockScheduledFuture<>(command, delay, unit);
+ mFutures.add(future);
+ return future;
+ }
+
+ @Override
+ public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period,
+ TimeUnit unit) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay,
+ long delay, TimeUnit unit) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void shutdown() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List<Runnable> shutdownNow() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isShutdown() {
+ return false;
+ }
+
+ @Override
+ public boolean isTerminated() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> Future<T> submit(Callable<T> task) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> Future<T> submit(Runnable task, T result) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Future<?> submit(Runnable command) {
+ mExecutes.add(command);
+ MockScheduledFuture<?> future = new MockScheduledFuture<>(command, 0,
+ TimeUnit.MILLISECONDS);
+ future.getCommand().run();
+ return future;
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
+ throws InterruptedException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout,
+ TimeUnit unit) throws InterruptedException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
+ throws ExecutionException, InterruptedException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+ throws ExecutionException, InterruptedException, TimeoutException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void execute(Runnable command) {
+ mExecutes.add(command);
+ command.run();
+ }
+
+ class MockScheduledFuture<V> implements ScheduledFuture<V> {
+
+ private final Runnable mCommand;
+ private final long mDelay;
+ private boolean mCancelled = false;
+
+ MockScheduledFuture(Runnable command, long delay, TimeUnit timeUnit) {
+ mCommand = command;
+ mDelay = delay;
+ }
+
+ public long getDelay() {
+ return mDelay;
+ }
+
+ public Runnable getCommand() {
+ return mCommand;
+ }
+
+ @Override
+ public long getDelay(TimeUnit unit) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int compareTo(Delayed o) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ mCancelled = true;
+ return mFutures.remove(this);
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return mCancelled;
+ }
+
+ @Override
+ public boolean isDone() {
+ return !mFutures.contains(this);
+ }
+
+ @Override
+ public V get() throws ExecutionException, InterruptedException {
+ return null;
+ }
+
+ @Override
+ public V get(long timeout, TimeUnit unit)
+ throws ExecutionException, InterruptedException, TimeoutException {
+ return null;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java b/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java
index ec4789ad0cdf..1ddc21e4ea4d 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java
@@ -20,15 +20,19 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import android.content.Context;
import android.content.LocusId;
import android.content.pm.ShortcutInfo;
import android.net.Uri;
+import androidx.test.InstrumentationRegistry;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.io.File;
import java.util.List;
@RunWith(JUnit4.class)
@@ -52,8 +56,12 @@ public final class PackageDataTest {
@Before
public void setUp() {
+ Context ctx = InstrumentationRegistry.getContext();
+ File testDir = new File(ctx.getCacheDir(), "testdir");
+ testDir.mkdir();
mPackageData = new PackageData(
- PACKAGE_NAME, USER_ID, pkg -> mIsDefaultDialer, pkg -> mIsDefaultSmsApp);
+ PACKAGE_NAME, USER_ID, pkg -> mIsDefaultDialer, pkg -> mIsDefaultSmsApp,
+ new MockScheduledExecutorService(), testDir, new ContactsQueryHelper(ctx));
ConversationInfo conversationInfo = new ConversationInfo.Builder()
.setShortcutId(SHORTCUT_ID)
.setLocusId(LOCUS_ID)
diff --git a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
index e4248a04878d..b273578ada3a 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
@@ -29,8 +29,11 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
+import android.content.Context;
import android.content.LocusId;
+import androidx.test.InstrumentationRegistry;
+
import com.android.server.LocalServices;
import org.junit.After;
@@ -41,10 +44,12 @@ import org.junit.runners.JUnit4;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Predicate;
@RunWith(JUnit4.class)
@@ -69,7 +74,13 @@ public final class UsageStatsQueryHelperTest {
addLocalServiceMock(UsageStatsManagerInternal.class, mUsageStatsManagerInternal);
- mPackageData = new TestPackageData(PKG_NAME, USER_ID_PRIMARY, pkg -> false, pkg -> false);
+ Context ctx = InstrumentationRegistry.getContext();
+ File testDir = new File(ctx.getCacheDir(), "testdir");
+ ScheduledExecutorService scheduledExecutorService = new MockScheduledExecutorService();
+ ContactsQueryHelper helper = new ContactsQueryHelper(ctx);
+
+ mPackageData = new TestPackageData(PKG_NAME, USER_ID_PRIMARY, pkg -> false, pkg -> false,
+ scheduledExecutorService, testDir, helper);
mPackageData.mConversationStore.mConversationInfo = new ConversationInfo.Builder()
.setShortcutId(SHORTCUT_ID)
.setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
@@ -175,7 +186,7 @@ public final class UsageStatsQueryHelperTest {
assertEquals(createInAppConversationEvent(130_000L, 30), events.get(2));
}
- private void addUsageEvents(UsageEvents.Event ... events) {
+ private void addUsageEvents(UsageEvents.Event... events) {
UsageEvents usageEvents = new UsageEvents(Arrays.asList(events), new String[]{});
when(mUsageStatsManagerInternal.queryEventsForUser(anyInt(), anyLong(), anyLong(),
anyBoolean(), anyBoolean())).thenReturn(usageEvents);
@@ -228,6 +239,12 @@ public final class UsageStatsQueryHelperTest {
private ConversationInfo mConversationInfo;
+ TestConversationStore(File packageDir,
+ ScheduledExecutorService scheduledExecutorService,
+ ContactsQueryHelper helper) {
+ super(packageDir, scheduledExecutorService, helper);
+ }
+
@Override
@Nullable
ConversationInfo getConversation(@Nullable String shortcutId) {
@@ -237,13 +254,18 @@ public final class UsageStatsQueryHelperTest {
private static class TestPackageData extends PackageData {
- private final TestConversationStore mConversationStore = new TestConversationStore();
+ private final TestConversationStore mConversationStore;
private final TestEventStore mEventStore = new TestEventStore();
TestPackageData(@NonNull String packageName, @UserIdInt int userId,
@NonNull Predicate<String> isDefaultDialerPredicate,
- @NonNull Predicate<String> isDefaultSmsAppPredicate) {
- super(packageName, userId, isDefaultDialerPredicate, isDefaultSmsAppPredicate);
+ @NonNull Predicate<String> isDefaultSmsAppPredicate,
+ @NonNull ScheduledExecutorService scheduledExecutorService, @NonNull File rootDir,
+ @NonNull ContactsQueryHelper helper) {
+ super(packageName, userId, isDefaultDialerPredicate, isDefaultSmsAppPredicate,
+ scheduledExecutorService, rootDir, helper);
+ mConversationStore = new TestConversationStore(rootDir, scheduledExecutorService,
+ helper);
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/power/PreRebootLoggerTest.java b/services/tests/servicestests/src/com/android/server/power/PreRebootLoggerTest.java
new file mode 100644
index 000000000000..a13823441665
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/PreRebootLoggerTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.test.FakeSettingsProvider;
+
+import com.google.common.io.Files;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+
+/**
+ * Tests for {@link PreRebootLogger}
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PreRebootLoggerTest {
+ @Mock Context mContext;
+ private MockContentResolver mContentResolver;
+ private File mDumpDir;
+
+ @BeforeClass
+ public static void setupOnce() {
+ FakeSettingsProvider.clearSettingsProvider();
+ }
+
+ @AfterClass
+ public static void tearDownOnce() {
+ FakeSettingsProvider.clearSettingsProvider();
+ }
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mContentResolver = new MockContentResolver(getInstrumentation().getTargetContext());
+ when(mContext.getContentResolver()).thenReturn(mContentResolver);
+ mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+
+ mDumpDir = Files.createTempDir();
+ mDumpDir.mkdir();
+ mDumpDir.deleteOnExit();
+ }
+
+ @Test
+ public void log_adbEnabled_dumpsInformationProperly() {
+ Settings.Global.putInt(mContentResolver, Settings.Global.ADB_ENABLED, 1);
+
+ PreRebootLogger.log(mContext, mDumpDir);
+
+ assertThat(mDumpDir.list()).asList().containsExactly("system", "package", "rollback");
+ }
+
+ @Test
+ public void log_adbDisabled_wipesDumpedInformation() {
+ Settings.Global.putInt(mContentResolver, Settings.Global.ADB_ENABLED, 1);
+ PreRebootLogger.log(mContext, mDumpDir);
+ Settings.Global.putInt(mContentResolver, Settings.Global.ADB_ENABLED, 0);
+
+ PreRebootLogger.log(mContext, mDumpDir);
+
+ assertThat(mDumpDir.listFiles()).isEmpty();
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java
new file mode 100644
index 000000000000..c1a1d5ecd3c8
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.testing.Assert.assertThrows;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
+import android.platform.test.annotations.Presubmit;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Test;
+
+@Presubmit
+public class DisplayAreaProviderTest {
+
+ @Test
+ public void testFromResources_emptyProvider() {
+ Assert.assertThat(DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider("")),
+ Matchers.instanceOf(DisplayAreaPolicy.Default.Provider.class));
+ }
+
+ @Test
+ public void testFromResources_nullProvider() {
+ Assert.assertThat(DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider(null)),
+ Matchers.instanceOf(DisplayAreaPolicy.Default.Provider.class));
+ }
+
+ @Test
+ public void testFromResources_customProvider() {
+ Assert.assertThat(DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider(
+ TestProvider.class.getName())), Matchers.instanceOf(TestProvider.class));
+ }
+
+ @Test
+ public void testFromResources_badProvider_notImplementingProviderInterface() {
+ assertThrows(IllegalStateException.class, () -> {
+ DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider(
+ Object.class.getName()));
+ });
+ }
+
+ @Test
+ public void testFromResources_badProvider_doesntExist() {
+ assertThrows(IllegalStateException.class, () -> {
+ DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider(
+ "com.android.wmtests.nonexistent.Provider"));
+ });
+ }
+
+ private static Resources resourcesWithProvider(String provider) {
+ Resources mock = mock(Resources.class);
+ when(mock.getString(
+ com.android.internal.R.string.config_deviceSpecificDisplayAreaPolicyProvider))
+ .thenReturn(provider);
+ return mock;
+ }
+
+ static class TestProvider implements DisplayAreaPolicy.Provider {
+
+ @Override
+ public DisplayAreaPolicy instantiate(WindowManagerService wmService, DisplayContent content,
+ DisplayArea.Root root, DisplayArea<? extends WindowContainer> imeContainer,
+ DisplayContent.TaskContainers taskContainers) {
+ throw new RuntimeException("test stub");
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index e71225579989..eae007d3a767 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -29,12 +29,13 @@ import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.spy;
import android.platform.test.annotations.Presubmit;
import android.util.IntArray;
@@ -122,13 +123,13 @@ public class InsetsPolicyTest extends WindowTestsBase {
// TODO: adjust this test if we pretend to the app that it's still able to control it.
@Test
public void testControlsForDispatch_forceStatusBarVisible() {
- addWindow(TYPE_STATUS_BAR, "topBar").mAttrs.privateFlags |=
+ addWindow(TYPE_STATUS_BAR, "statusBar").mAttrs.privateFlags |=
PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
addWindow(TYPE_NAVIGATION_BAR, "navBar");
final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
- // The app must not control the top bar.
+ // The app must not control the status bar.
assertNotNull(controls);
assertEquals(1, controls.length);
}
@@ -137,6 +138,7 @@ public class InsetsPolicyTest extends WindowTestsBase {
public void testControlsForDispatch_statusBarForceShowNavigation() {
addWindow(TYPE_NOTIFICATION_SHADE, "notificationShade").mAttrs.privateFlags |=
PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
+ addWindow(TYPE_STATUS_BAR, "statusBar");
addWindow(TYPE_NAVIGATION_BAR, "navBar");
final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
@@ -169,7 +171,8 @@ public class InsetsPolicyTest extends WindowTestsBase {
.getControllableInsetProvider().getSource().setVisible(false);
final WindowState app = addWindow(TYPE_APPLICATION, "app");
- final InsetsPolicy policy = mDisplayContent.getInsetsPolicy();
+ final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy());
+ doNothing().when(policy).startAnimation(anyBoolean(), any());
policy.updateBarControlTarget(app);
policy.showTransient(
IntArray.wrap(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}));
@@ -184,7 +187,7 @@ public class InsetsPolicyTest extends WindowTestsBase {
}
@Test
- public void testShowTransientBars_topCanBeTransient_appGetsTopFakeControl() {
+ public void testShowTransientBars_statusBarCanBeTransient_appGetsStatusBarFakeControl() {
// Adding app window before setting source visibility is to prevent the visibility from
// being cleared by InsetsSourceProvider.updateVisibility.
final WindowState app = addWindow(TYPE_APPLICATION, "app");
@@ -194,14 +197,15 @@ public class InsetsPolicyTest extends WindowTestsBase {
addWindow(TYPE_NAVIGATION_BAR, "navBar")
.getControllableInsetProvider().getSource().setVisible(true);
- final InsetsPolicy policy = mDisplayContent.getInsetsPolicy();
+ final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy());
+ doNothing().when(policy).startAnimation(anyBoolean(), any());
policy.updateBarControlTarget(app);
policy.showTransient(
IntArray.wrap(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}));
final InsetsSourceControl[] controls =
mDisplayContent.getInsetsStateController().getControlsForDispatch(app);
- // The app must get the fake control of the top bar, and must get the real control of the
+ // The app must get the fake control of the status bar, and must get the real control of the
// navigation bar.
assertEquals(2, controls.length);
for (int i = controls.length - 1; i >= 0; i--) {
@@ -222,7 +226,8 @@ public class InsetsPolicyTest extends WindowTestsBase {
.getControllableInsetProvider().getSource().setVisible(false);
final WindowState app = addWindow(TYPE_APPLICATION, "app");
- final InsetsPolicy policy = mDisplayContent.getInsetsPolicy();
+ final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy());
+ doNothing().when(policy).startAnimation(anyBoolean(), any());
policy.updateBarControlTarget(app);
policy.showTransient(
IntArray.wrap(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}));
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 39cdd2cb907e..5cf1fbbacaf4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -20,7 +20,9 @@ import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
@@ -33,14 +35,14 @@ import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.test.InsetsModeSession;
+import androidx.test.filters.FlakyTest;
+import androidx.test.filters.SmallTest;
+
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.SmallTest;
-
@SmallTest
@FlakyTest(detail = "Promote to pre-submit once confirmed stable.")
@Presubmit
@@ -88,6 +90,10 @@ public class InsetsStateControllerTest extends WindowTestsBase {
final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar");
final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
final WindowState ime = createWindow(null, TYPE_APPLICATION, "ime");
+
+ // IME cannot be the IME target.
+ ime.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
+
getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null);
getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null);
getController().getSourceProvider(ITYPE_IME).setWindow(ime, null, null);
@@ -98,6 +104,10 @@ public class InsetsStateControllerTest extends WindowTestsBase {
public void testImeForDispatch() {
final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
final WindowState ime = createWindow(null, TYPE_APPLICATION, "ime");
+
+ // IME cannot be the IME target.
+ ime.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
+
InsetsSourceProvider statusBarProvider =
getController().getSourceProvider(ITYPE_STATUS_BAR);
statusBarProvider.setWindow(statusBar, null, ((displayFrames, windowState, rect) ->
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
index f7aa3cc9e52c..0312df6d34fc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
@@ -34,14 +34,21 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityManager.StackInfo;
+import android.app.PictureInPictureParams;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder;
@@ -49,6 +56,8 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.util.ArrayMap;
+import android.content.pm.ActivityInfo;
+import android.util.Rational;
import android.view.Display;
import android.view.ITaskOrganizer;
import android.view.IWindowContainer;
@@ -384,11 +393,19 @@ public class TaskOrganizerTests extends WindowTestsBase {
RunningTaskInfo info2 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ final int initialRootTaskCount = mWm.mAtmService.mTaskOrganizerController.getRootTasks(
+ mDisplayContent.mDisplayId, null /* activityTypes */).size();
+
final ActivityStack stack = createTaskStackOnDisplay(
WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent);
final ActivityStack stack2 = createTaskStackOnDisplay(
WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, mDisplayContent);
+ // Check getRootTasks works
+ List<RunningTaskInfo> roots = mWm.mAtmService.mTaskOrganizerController.getRootTasks(
+ mDisplayContent.mDisplayId, null /* activityTypes */);
+ assertEquals(initialRootTaskCount + 2, roots.size());
+
lastReportedTiles.clear();
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reparent(stack.mRemoteToken, info1.token, true /* onTop */);
@@ -415,11 +432,18 @@ public class TaskOrganizerTests extends WindowTestsBase {
// Check the getChildren call
List<RunningTaskInfo> children =
- mWm.mAtmService.mTaskOrganizerController.getChildTasks(info1.token);
+ mWm.mAtmService.mTaskOrganizerController.getChildTasks(info1.token,
+ null /* activityTypes */);
assertEquals(2, children.size());
- children = mWm.mAtmService.mTaskOrganizerController.getChildTasks(info2.token);
+ children = mWm.mAtmService.mTaskOrganizerController.getChildTasks(info2.token,
+ null /* activityTypes */);
assertEquals(0, children.size());
+ // Check that getRootTasks doesn't include children of tiles
+ roots = mWm.mAtmService.mTaskOrganizerController.getRootTasks(mDisplayContent.mDisplayId,
+ null /* activityTypes */);
+ assertEquals(initialRootTaskCount, roots.size());
+
lastReportedTiles.clear();
wct = new WindowContainerTransaction();
wct.reorder(stack2.mRemoteToken, true /* onTop */);
@@ -483,4 +507,76 @@ public class TaskOrganizerTests extends WindowTestsBase {
verify(transactionListener)
.transactionReady(anyInt(), any());
}
+
+ class StubOrganizer extends ITaskOrganizer.Stub {
+ RunningTaskInfo mInfo;
+
+ @Override
+ public void taskAppeared(RunningTaskInfo info) {
+ mInfo = info;
+ }
+ @Override
+ public void taskVanished(IWindowContainer wc) {
+ }
+ @Override
+ public void transactionReady(int id, SurfaceControl.Transaction t) {
+ }
+ @Override
+ public void onTaskInfoChanged(RunningTaskInfo info) {
+ }
+ };
+
+ private ActivityRecord makePipableActivity() {
+ final ActivityRecord record = createActivityRecord(mDisplayContent,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+ record.info.flags |= ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE;
+ spyOn(record);
+ doReturn(true).when(record).checkEnterPictureInPictureState(any(), anyBoolean());
+ return record;
+ }
+
+ @Test
+ public void testEnterPipParams() {
+ final StubOrganizer o = new StubOrganizer();
+ mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(o, WINDOWING_MODE_PINNED);
+ final ActivityRecord record = makePipableActivity();
+
+ final PictureInPictureParams p =
+ new PictureInPictureParams.Builder().setAspectRatio(new Rational(1, 2)).build();
+ assertTrue(mWm.mAtmService.enterPictureInPictureMode(record.token, p));
+ waitUntilHandlersIdle();
+ assertNotNull(o.mInfo);
+ assertNotNull(o.mInfo.pictureInPictureParams);
+ }
+
+ @Test
+ public void testChangePipParams() {
+ class ChangeSavingOrganizer extends StubOrganizer {
+ RunningTaskInfo mChangedInfo;
+ @Override
+ public void onTaskInfoChanged(RunningTaskInfo info) {
+ mChangedInfo = info;
+ }
+ }
+ ChangeSavingOrganizer o = new ChangeSavingOrganizer();
+ mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(o, WINDOWING_MODE_PINNED);
+
+ final ActivityRecord record = makePipableActivity();
+ final PictureInPictureParams p =
+ new PictureInPictureParams.Builder().setAspectRatio(new Rational(1, 2)).build();
+ assertTrue(mWm.mAtmService.enterPictureInPictureMode(record.token, p));
+ waitUntilHandlersIdle();
+ assertNotNull(o.mInfo);
+ assertNotNull(o.mInfo.pictureInPictureParams);
+
+ final PictureInPictureParams p2 =
+ new PictureInPictureParams.Builder().setAspectRatio(new Rational(3, 4)).build();
+ mWm.mAtmService.setPictureInPictureParams(record.token, p2);
+ waitUntilHandlersIdle();
+ assertNotNull(o.mChangedInfo);
+ assertNotNull(o.mChangedInfo.pictureInPictureParams);
+ final Rational ratio = o.mChangedInfo.pictureInPictureParams.getAspectRatioRational();
+ assertEquals(3, ratio.getNumerator());
+ assertEquals(4, ratio.getDenominator());
+ }
}
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index f54f8d1f5832..ec99f36f6e70 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -573,6 +573,7 @@ public final class Call {
/**
* Indicates that the call is an adhoc conference call. This property can be set for both
* incoming and outgoing calls.
+ * @hide
*/
public static final int PROPERTY_IS_ADHOC_CONFERENCE = 0x00002000;
diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java
index 6b0845f5d12b..56acdff530eb 100644
--- a/telecomm/java/android/telecom/Conference.java
+++ b/telecomm/java/android/telecom/Conference.java
@@ -174,6 +174,7 @@ public abstract class Conference extends Conferenceable {
/**
* Returns whether this conference is requesting that the system play a ringback tone
* on its behalf.
+ * @hide
*/
public final boolean isRingbackRequested() {
return mRingbackRequested;
@@ -324,6 +325,7 @@ public abstract class Conference extends Conferenceable {
* the default dialer's {@link InCallService}.
*
* @param videoState The video state in which to answer the connection.
+ * @hide
*/
public void onAnswer(int videoState) {}
@@ -343,6 +345,7 @@ public abstract class Conference extends Conferenceable {
* a request to reject.
* For managed {@link ConnectionService}s, this will be called when the user rejects a call via
* the default dialer's {@link InCallService}.
+ * @hide
*/
public void onReject() {}
@@ -362,6 +365,7 @@ public abstract class Conference extends Conferenceable {
/**
* Sets state to be ringing.
+ * @hide
*/
public final void setRinging() {
setState(Connection.STATE_RINGING);
@@ -487,6 +491,7 @@ public abstract class Conference extends Conferenceable {
* that do not play a ringback tone themselves in the conference's audio stream.
*
* @param ringback Whether the ringback tone is to be played.
+ * @hide
*/
public final void setRingbackRequested(boolean ringback) {
if (mRingbackRequested != ringback) {
@@ -736,6 +741,7 @@ public abstract class Conference extends Conferenceable {
*
* @param disconnectCause The disconnect cause, ({@see android.telecomm.DisconnectCause}).
* @return A {@code Conference} which indicates failure.
+ * @hide
*/
public @NonNull static Conference createFailedConference(
@NonNull DisconnectCause disconnectCause, @NonNull PhoneAccountHandle phoneAccount) {
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 72c66d20548a..8049459cf3f4 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -501,7 +501,7 @@ public abstract class Connection extends Conferenceable {
* Set by the framework to indicate that it is an adhoc conference call.
* <p>
* This is used for Outgoing and incoming conference calls.
- *
+ * @hide
*/
public static final int PROPERTY_IS_ADHOC_CONFERENCE = 1 << 12;
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index bf4dee2c1f04..a28cc4f69155 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1875,8 +1875,8 @@ public class TelecomManager {
* {@link #registerPhoneAccount}.
* @param extras A bundle that will be passed through to
* {@link ConnectionService#onCreateIncomingConference}.
+ * @hide
*/
-
public void addNewIncomingConference(@NonNull PhoneAccountHandle phoneAccount,
@NonNull Bundle extras) {
try {
@@ -2115,6 +2115,7 @@ public class TelecomManager {
*
* @param participants List of participants to start conference with
* @param extras Bundle of extras to use with the call
+ * @hide
*/
@RequiresPermission(android.Manifest.permission.CALL_PHONE)
public void startConference(@NonNull List<Uri> participants,
diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
index 553bcff931d2..e97cfaf0afa6 100644
--- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
+++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
@@ -123,6 +123,14 @@ public final class CarrierAppUtils {
return userContext.getContentResolver();
}
+ private static boolean isUpdatedSystemApp(ApplicationInfo ai) {
+ if ((ai.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
+ return true;
+ }
+
+ return false;
+ }
+
/**
* Disable carrier apps until they are privileged
* Must be public b/c framework unit tests can't access package-private methods.
@@ -137,7 +145,7 @@ public final class CarrierAppUtils {
PackageManager packageManager = context.getPackageManager();
PermissionManager permissionManager =
(PermissionManager) context.getSystemService(Context.PERMISSION_SERVICE);
- List<ApplicationInfo> candidates = getDefaultNotUpdatedCarrierAppCandidatesHelper(
+ List<ApplicationInfo> candidates = getDefaultCarrierAppCandidatesHelper(
userId, systemCarrierAppsDisabledUntilUsed, context);
if (candidates == null || candidates.isEmpty()) {
return;
@@ -176,7 +184,7 @@ public final class CarrierAppUtils {
if (hasPrivileges) {
// Only update enabled state for the app on /system. Once it has been
// updated we shouldn't touch it.
- if (enabledSetting
+ if (!isUpdatedSystemApp(ai) && enabledSetting
== PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
|| enabledSetting
== PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
@@ -230,7 +238,7 @@ public final class CarrierAppUtils {
} else { // No carrier privileges
// Only update enabled state for the app on /system. Once it has been
// updated we shouldn't touch it.
- if (enabledSetting
+ if (!isUpdatedSystemApp(ai) && enabledSetting
== PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
&& (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0) {
Log.i(TAG, "Update state(" + packageName
@@ -361,29 +369,6 @@ public final class CarrierAppUtils {
return apps;
}
- private static List<ApplicationInfo> getDefaultNotUpdatedCarrierAppCandidatesHelper(
- int userId, ArraySet<String> systemCarrierAppsDisabledUntilUsed, Context context) {
- if (systemCarrierAppsDisabledUntilUsed == null) {
- return null;
- }
-
- int size = systemCarrierAppsDisabledUntilUsed.size();
- if (size == 0) {
- return null;
- }
-
- List<ApplicationInfo> apps = new ArrayList<>(size);
- for (int i = 0; i < size; i++) {
- String packageName = systemCarrierAppsDisabledUntilUsed.valueAt(i);
- ApplicationInfo ai =
- getApplicationInfoIfNotUpdatedSystemApp(userId, packageName, context);
- if (ai != null) {
- apps.add(ai);
- }
- }
- return apps;
- }
-
private static Map<String, List<ApplicationInfo>> getDefaultCarrierAssociatedAppsHelper(
int userId, ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed,
Context context) {
@@ -395,11 +380,11 @@ public final class CarrierAppUtils {
systemCarrierAssociatedAppsDisabledUntilUsed.valueAt(i);
for (int j = 0; j < associatedAppPackages.size(); j++) {
ApplicationInfo ai =
- getApplicationInfoIfNotUpdatedSystemApp(
+ getApplicationInfoIfSystemApp(
userId, associatedAppPackages.get(j), context);
// Only update enabled state for the app on /system. Once it has been updated we
// shouldn't touch it.
- if (ai != null) {
+ if (ai != null && !isUpdatedSystemApp(ai)) {
List<ApplicationInfo> appList = associatedApps.get(carrierAppPackage);
if (appList == null) {
appList = new ArrayList<>();
@@ -413,26 +398,6 @@ public final class CarrierAppUtils {
}
@Nullable
- private static ApplicationInfo getApplicationInfoIfNotUpdatedSystemApp(
- int userId, String packageName, Context context) {
- try {
- ApplicationInfo ai = context.createContextAsUser(UserHandle.of(userId), 0)
- .getPackageManager()
- .getApplicationInfo(packageName,
- PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
- | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
- | PackageManager.MATCH_SYSTEM_ONLY
- | PackageManager.MATCH_FACTORY_ONLY);
- if (ai != null) {
- return ai;
- }
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "Could not reach PackageManager", e);
- }
- return null;
- }
-
- @Nullable
private static ApplicationInfo getApplicationInfoIfSystemApp(
int userId, String packageName, Context context) {
try {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 945c8888f402..ebb53c50ca98 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1084,6 +1084,7 @@ public class CarrierConfigManager {
/**
* Determines whether adhoc conference calls are supported by a carrier. When {@code true},
* adhoc conference calling is supported, {@code false otherwise}.
+ * @hide
*/
public static final String KEY_SUPPORT_ADHOC_CONFERENCE_CALLS_BOOL =
"support_adhoc_conference_calls_bool";
diff --git a/tests/BootImageProfileTest/OWNERS b/tests/BootImageProfileTest/OWNERS
new file mode 100644
index 000000000000..657b3f2add2e
--- /dev/null
+++ b/tests/BootImageProfileTest/OWNERS
@@ -0,0 +1,4 @@
+mathieuc@google.com
+calin@google.com
+yawanng@google.com
+sehr@google.com
diff --git a/tests/PlatformCompatGating/Android.bp b/tests/PlatformCompatGating/Android.bp
index 74dfde848191..342c47de755a 100644
--- a/tests/PlatformCompatGating/Android.bp
+++ b/tests/PlatformCompatGating/Android.bp
@@ -18,6 +18,7 @@ android_test {
name: "PlatformCompatGating",
// Only compile source java files in this apk.
srcs: ["src/**/*.java"],
+ test_suites: ["device-tests"],
static_libs: [
"junit",
"androidx.test.runner",
diff --git a/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java b/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java
index 2c2e8282ff51..0393248c34d2 100644
--- a/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java
+++ b/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java
@@ -16,22 +16,98 @@
package com.android.tests.rollback.host;
+import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertThrows;
+
+import com.android.tradefed.device.LogcatReceiver;
+import com.android.tradefed.result.InputStreamSource;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Runs the network rollback tests.
*/
@RunWith(DeviceJUnit4ClassRunner.class)
public class NetworkStagedRollbackTest extends BaseHostJUnit4Test {
/**
+ * Runs the given phase of a test by calling into the device.
+ * Throws an exception if the test phase fails.
+ * <p>
+ * For example, <code>runPhase("testApkOnlyEnableRollback");</code>
+ */
+ private void runPhase(String phase) throws Exception {
+ assertTrue(runDeviceTests("com.android.tests.rollback",
+ "com.android.tests.rollback.NetworkStagedRollbackTest",
+ phase));
+ }
+
+ private static final String REASON_EXPLICIT_HEALTH_CHECK = "REASON_EXPLICIT_HEALTH_CHECK";
+
+ private static final String ROLLBACK_INITIATE = "ROLLBACK_INITIATE";
+ private static final String ROLLBACK_BOOT_TRIGGERED = "ROLLBACK_BOOT_TRIGGERED";
+
+ private LogcatReceiver mReceiver;
+
+ @Before
+ public void setUp() throws Exception {
+ mReceiver = new LogcatReceiver(getDevice(), "logcat -s WatchdogRollbackLogger",
+ getDevice().getOptions().getMaxLogcatDataSize(), 0);
+ mReceiver.start();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mReceiver.stop();
+ mReceiver.clear();
+ }
+
+ /**
* Tests failed network health check triggers watchdog staged rollbacks.
*/
@Test
public void testNetworkFailedRollback() throws Exception {
+ try {
+ // Disconnect internet so we can test network health triggered rollbacks
+ getDevice().executeShellCommand("svc wifi disable");
+ getDevice().executeShellCommand("svc data disable");
+
+ runPhase("testNetworkFailedRollback_Phase1");
+ // Reboot device to activate staged package
+ getDevice().reboot();
+
+ // Verify rollback was enabled
+ runPhase("testNetworkFailedRollback_Phase2");
+ assertThrows(AssertionError.class, () -> runPhase("testNetworkFailedRollback_Phase3"));
+
+ getDevice().waitForDeviceAvailable();
+ // Verify rollback was executed after health check deadline
+ runPhase("testNetworkFailedRollback_Phase4");
+ InputStreamSource logcatStream = mReceiver.getLogcatData();
+ try {
+ List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream);
+ assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null,
+ REASON_EXPLICIT_HEALTH_CHECK, null));
+ assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null,
+ null, null));
+ } finally {
+ logcatStream.close();
+ }
+ } finally {
+ // Reconnect internet again so we won't break tests which assume internet available
+ getDevice().executeShellCommand("svc wifi enable");
+ getDevice().executeShellCommand("svc data enable");
+ }
}
/**
@@ -40,4 +116,57 @@ public class NetworkStagedRollbackTest extends BaseHostJUnit4Test {
@Test
public void testNetworkPassedDoesNotRollback() throws Exception {
}
+
+ /**
+ * Returns a list of all Watchdog logging events which have occurred.
+ */
+ private List<String> getWatchdogLoggingEvents(InputStreamSource inputStreamSource)
+ throws Exception {
+ List<String> watchdogEvents = new ArrayList<>();
+ InputStream inputStream = inputStreamSource.createInputStream();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (line.contains("Watchdog event occurred")) {
+ watchdogEvents.add(line);
+ }
+ }
+ return watchdogEvents;
+ }
+
+ /**
+ * Returns whether a Watchdog event has occurred that matches the given criteria.
+ *
+ * Check the value of all non-null parameters against the list of Watchdog events that have
+ * occurred, and return {@code true} if an event exists which matches all criteria.
+ */
+ private boolean watchdogEventOccurred(List<String> loggingEvents,
+ String type, String logPackage,
+ String rollbackReason, String failedPackageName) throws Exception {
+ List<String> eventCriteria = new ArrayList<>();
+ if (type != null) {
+ eventCriteria.add("type: " + type);
+ }
+ if (logPackage != null) {
+ eventCriteria.add("logPackage: " + logPackage);
+ }
+ if (rollbackReason != null) {
+ eventCriteria.add("rollbackReason: " + rollbackReason);
+ }
+ if (failedPackageName != null) {
+ eventCriteria.add("failedPackageName: " + failedPackageName);
+ }
+ for (String loggingEvent: loggingEvents) {
+ boolean matchesCriteria = true;
+ for (String criterion: eventCriteria) {
+ if (!loggingEvent.contains(criterion)) {
+ matchesCriteria = false;
+ }
+ }
+ if (matchesCriteria) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java
index 04004d6abb5a..e5c8a685813f 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java
@@ -16,9 +16,143 @@
package com.android.tests.rollback;
+import static com.android.cts.rollback.lib.RollbackInfoSubject.assertThat;
+import static com.android.cts.rollback.lib.RollbackUtils.getUniqueRollbackInfoForPackage;
+
+import android.Manifest;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.rollback.RollbackManager;
+import android.os.ParcelFileDescriptor;
+import android.provider.DeviceConfig;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.TestApp;
+import com.android.cts.rollback.lib.RollbackUtils;
+
+import libcore.io.IoUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.io.File;
+import java.util.concurrent.TimeUnit;
+
@RunWith(JUnit4.class)
public class NetworkStagedRollbackTest {
+ private static final String NETWORK_STACK_CONNECTOR_CLASS =
+ "android.net.INetworkStackConnector";
+ private static final String PROPERTY_WATCHDOG_REQUEST_TIMEOUT_MILLIS =
+ "watchdog_request_timeout_millis";
+
+ private static final TestApp NETWORK_STACK = new TestApp("NetworkStack",
+ getNetworkStackPackageName(), -1, false, findNetworkStackApk());
+
+ private static File findNetworkStackApk() {
+ final File apk = new File("/system/priv-app/NetworkStack/NetworkStack.apk");
+ if (apk.isFile()) {
+ return apk;
+ }
+ return new File("/system/priv-app/NetworkStackNext/NetworkStackNext.apk");
+ }
+
+ /**
+ * Adopts common shell permissions needed for rollback tests.
+ */
+ @Before
+ public void adoptShellPermissions() {
+ InstallUtils.adoptShellPermissionIdentity(
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.DELETE_PACKAGES,
+ Manifest.permission.TEST_MANAGE_ROLLBACKS,
+ Manifest.permission.FORCE_STOP_PACKAGES,
+ Manifest.permission.WRITE_DEVICE_CONFIG);
+ }
+
+ /**
+ * Drops shell permissions needed for rollback tests.
+ */
+ @After
+ public void dropShellPermissions() {
+ InstallUtils.dropShellPermissionIdentity();
+ }
+
+ @Test
+ public void testNetworkFailedRollback_Phase1() throws Exception {
+ // Remove available rollbacks and uninstall NetworkStack on /data/
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ String networkStack = getNetworkStackPackageName();
+
+ rm.expireRollbackForPackage(networkStack);
+ uninstallNetworkStackPackage();
+
+ assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
+ networkStack)).isNull();
+
+ // Reduce health check deadline
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+ PROPERTY_WATCHDOG_REQUEST_TIMEOUT_MILLIS,
+ Integer.toString(120000), false);
+ // Simulate re-installation of new NetworkStack with rollbacks enabled
+ installNetworkStackPackage();
+ }
+
+ @Test
+ public void testNetworkFailedRollback_Phase2() throws Exception {
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
+ getNetworkStackPackageName())).isNotNull();
+
+ // Sleep for < health check deadline
+ Thread.sleep(TimeUnit.SECONDS.toMillis(5));
+ // Verify rollback was not executed before health check deadline
+ assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
+ getNetworkStackPackageName())).isNull();
+ }
+
+ @Test
+ public void testNetworkFailedRollback_Phase3() throws Exception {
+ // Sleep for > health check deadline (120s to trigger rollback + 120s to reboot)
+ // The device is expected to reboot during sleeping. This device method will fail and
+ // the host will catch the assertion. If reboot doesn't happen, the host will fail the
+ // assertion.
+ Thread.sleep(TimeUnit.SECONDS.toMillis(240));
+ }
+
+ @Test
+ public void testNetworkFailedRollback_Phase4() throws Exception {
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
+ getNetworkStackPackageName())).isNotNull();
+ }
+
+ private static String getNetworkStackPackageName() {
+ Intent intent = new Intent(NETWORK_STACK_CONNECTOR_CLASS);
+ ComponentName comp = intent.resolveSystemService(
+ InstrumentationRegistry.getInstrumentation().getContext().getPackageManager(), 0);
+ return comp.getPackageName();
+ }
+
+ private static void installNetworkStackPackage() throws Exception {
+ Install.single(NETWORK_STACK).setStaged().setEnableRollback()
+ .addInstallFlags(PackageManager.INSTALL_REPLACE_EXISTING).commit();
+ }
+
+ private static void uninstallNetworkStackPackage() {
+ // Uninstall the package as a privileged user so we won't fail due to permission.
+ runShellCommand("pm uninstall " + getNetworkStackPackageName());
+ }
+
+ private static void runShellCommand(String cmd) {
+ ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .executeShellCommand(cmd);
+ IoUtils.closeQuietly(pfd);
+ }
}
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index 0fdcbc52bffa..bddd93c6de36 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -274,55 +274,6 @@ public class StagedRollbackTest {
TestApp.B)).isNotNull();
}
- @Test
- public void testNetworkFailedRollback_Phase1() throws Exception {
- // Remove available rollbacks and uninstall NetworkStack on /data/
- RollbackManager rm = RollbackUtils.getRollbackManager();
- String networkStack = getNetworkStackPackageName();
-
- rm.expireRollbackForPackage(networkStack);
- uninstallNetworkStackPackage();
-
- assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
- networkStack)).isNull();
-
- // Reduce health check deadline
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
- PROPERTY_WATCHDOG_REQUEST_TIMEOUT_MILLIS,
- Integer.toString(120000), false);
- // Simulate re-installation of new NetworkStack with rollbacks enabled
- installNetworkStackPackage();
- }
-
- @Test
- public void testNetworkFailedRollback_Phase2() throws Exception {
- RollbackManager rm = RollbackUtils.getRollbackManager();
- assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
- getNetworkStackPackageName())).isNotNull();
-
- // Sleep for < health check deadline
- Thread.sleep(TimeUnit.SECONDS.toMillis(5));
- // Verify rollback was not executed before health check deadline
- assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
- getNetworkStackPackageName())).isNull();
- }
-
- @Test
- public void testNetworkFailedRollback_Phase3() throws Exception {
- // Sleep for > health check deadline (120s to trigger rollback + 120s to reboot)
- // The device is expected to reboot during sleeping. This device method will fail and
- // the host will catch the assertion. If reboot doesn't happen, the host will fail the
- // assertion.
- Thread.sleep(TimeUnit.SECONDS.toMillis(240));
- }
-
- @Test
- public void testNetworkFailedRollback_Phase4() throws Exception {
- RollbackManager rm = RollbackUtils.getRollbackManager();
- assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
- getNetworkStackPackageName())).isNotNull();
- }
-
private static String getNetworkStackPackageName() {
Intent intent = new Intent(NETWORK_STACK_CONNECTOR_CLASS);
ComponentName comp = intent.resolveSystemService(
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index 032f18240a55..3c5eaef86bcc 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -228,44 +228,6 @@ public class StagedRollbackTest extends BaseHostJUnit4Test {
}
/**
- * Tests failed network health check triggers watchdog staged rollbacks.
- */
- @Test
- public void testNetworkFailedRollback() throws Exception {
- try {
- // Disconnect internet so we can test network health triggered rollbacks
- getDevice().executeShellCommand("svc wifi disable");
- getDevice().executeShellCommand("svc data disable");
-
- runPhase("testNetworkFailedRollback_Phase1");
- // Reboot device to activate staged package
- getDevice().reboot();
-
- // Verify rollback was enabled
- runPhase("testNetworkFailedRollback_Phase2");
- assertThrows(AssertionError.class, () -> runPhase("testNetworkFailedRollback_Phase3"));
-
- getDevice().waitForDeviceAvailable();
- // Verify rollback was executed after health check deadline
- runPhase("testNetworkFailedRollback_Phase4");
- InputStreamSource logcatStream = mReceiver.getLogcatData();
- try {
- List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream);
- assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null,
- REASON_EXPLICIT_HEALTH_CHECK, null));
- assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null,
- null, null));
- } finally {
- logcatStream.close();
- }
- } finally {
- // Reconnect internet again so we won't break tests which assume internet available
- getDevice().executeShellCommand("svc wifi enable");
- getDevice().executeShellCommand("svc data enable");
- }
- }
-
- /**
* Tests passed network health check does not trigger watchdog staged rollbacks.
*/
@Test
diff --git a/tests/RollbackTest/TEST_MAPPING b/tests/RollbackTest/TEST_MAPPING
index fefde5b4be12..0f4c4603f9b4 100644
--- a/tests/RollbackTest/TEST_MAPPING
+++ b/tests/RollbackTest/TEST_MAPPING
@@ -7,6 +7,9 @@
"name": "StagedRollbackTest"
},
{
+ "name": "NetworkStagedRollbackTest"
+ },
+ {
"name": "MultiUserRollbackTest"
}
]
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index d4e024dbe0c7..1c330e263d7e 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -272,4 +272,8 @@ interface IWifiManager
boolean isScanThrottleEnabled();
Map getAllMatchingPasspointProfilesForScanResults(in List<ScanResult> scanResult);
+
+ void setAutoWakeupEnabled(boolean enable);
+
+ boolean isAutoWakeupEnabled();
}
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index a720236689d3..0cce23d196cf 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -951,7 +951,7 @@ public class WifiConfiguration implements Parcelable {
* Indicate whether the network is trusted or not. Networks are considered trusted
* if the user explicitly allowed this network connection.
* This bit can be used by suggestion network, see
- * {@link WifiNetworkSuggestion.Builder#setUnTrusted(boolean)}
+ * {@link WifiNetworkSuggestion.Builder#setUntrusted(boolean)}
* @hide
*/
public boolean trusted;
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index fb30910c17b1..b6f4490a1872 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -6163,4 +6163,50 @@ public class WifiManager {
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Enable/disable wifi auto wakeup feature.
+ *
+ * <p>
+ * The feature is described in
+ * <a href="Wi-Fi Turn on automatically">
+ * https://source.android.com/devices/tech/connect/wifi-infrastructure
+ * #turn_on_wi-fi_automatically
+ * </a>
+ *
+ * @param enable true to enable, false to disable.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ public void setAutoWakeupEnabled(boolean enable) {
+ try {
+ mService.setAutoWakeupEnabled(enable);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get the persisted Wi-Fi auto wakeup feature state. Defaults to false, unless changed by the
+ * user via Settings.
+ *
+ * <p>
+ * The feature is described in
+ * <a href="Wi-Fi Turn on automatically">
+ * https://source.android.com/devices/tech/connect/wifi-infrastructure
+ * #turn_on_wi-fi_automatically
+ * </a>
+ *
+ * @return true to indicate that wakeup feature is enabled, false to indicate that wakeup
+ * feature is disabled.
+ */
+ @RequiresPermission(ACCESS_WIFI_STATE)
+ public boolean isAutoWakeupEnabled() {
+ try {
+ return mService.isAutoWakeupEnabled();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/wifi/java/android/net/wifi/wificond/NativeScanResult.java b/wifi/java/android/net/wifi/wificond/NativeScanResult.java
index 7cc617d61b00..bd99476afe43 100644
--- a/wifi/java/android/net/wifi/wificond/NativeScanResult.java
+++ b/wifi/java/android/net/wifi/wificond/NativeScanResult.java
@@ -16,14 +16,21 @@
package android.net.wifi.wificond;
+import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.net.MacAddress;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
/**
@@ -33,6 +40,8 @@ import java.util.List;
*/
@SystemApi
public final class NativeScanResult implements Parcelable {
+ private static final String TAG = "NativeScanResult";
+
/** @hide */
@VisibleForTesting
public byte[] ssid;
@@ -53,7 +62,7 @@ public final class NativeScanResult implements Parcelable {
public long tsf;
/** @hide */
@VisibleForTesting
- public int capability;
+ @BssCapabilityBits public int capability;
/** @hide */
@VisibleForTesting
public boolean associated;
@@ -71,14 +80,17 @@ public final class NativeScanResult implements Parcelable {
}
/**
- * Returns raw bytes representing the MAC address (BSSID) of the AP represented by this scan
- * result.
+ * Returns the MAC address (BSSID) of the AP represented by this scan result.
*
- * @return a byte array, possibly null or containing the incorrect number of bytes for a MAC
- * address.
+ * @return a MacAddress or null on error.
*/
- @NonNull public byte[] getBssid() {
- return bssid;
+ @Nullable public MacAddress getBssid() {
+ try {
+ return MacAddress.fromBytes(bssid);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Illegal argument " + Arrays.toString(bssid), e);
+ return null;
+ }
}
/**
@@ -127,31 +139,103 @@ public final class NativeScanResult implements Parcelable {
return associated;
}
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = {"BSS_CAPABILITY_"},
+ value = {BSS_CAPABILITY_ESS,
+ BSS_CAPABILITY_IBSS,
+ BSS_CAPABILITY_CF_POLLABLE,
+ BSS_CAPABILITY_CF_POLL_REQUEST,
+ BSS_CAPABILITY_PRIVACY,
+ BSS_CAPABILITY_SHORT_PREAMBLE,
+ BSS_CAPABILITY_PBCC,
+ BSS_CAPABILITY_CHANNEL_AGILITY,
+ BSS_CAPABILITY_SPECTRUM_MANAGEMENT,
+ BSS_CAPABILITY_QOS,
+ BSS_CAPABILITY_SHORT_SLOT_TIME,
+ BSS_CAPABILITY_APSD,
+ BSS_CAPABILITY_RADIO_MANAGEMENT,
+ BSS_CAPABILITY_DSSS_OFDM,
+ BSS_CAPABILITY_DELAYED_BLOCK_ACK,
+ BSS_CAPABILITY_IMMEDIATE_BLOCK_ACK
+ })
+ public @interface BssCapabilityBits { }
+
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): ESS.
+ */
+ public static final int BSS_CAPABILITY_ESS = 0x1 << 0;
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): IBSS.
+ */
+ public static final int BSS_CAPABILITY_IBSS = 0x1 << 1;
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): CF Pollable.
+ */
+ public static final int BSS_CAPABILITY_CF_POLLABLE = 0x1 << 2;
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): CF-Poll Request.
+ */
+ public static final int BSS_CAPABILITY_CF_POLL_REQUEST = 0x1 << 3;
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Privacy.
+ */
+ public static final int BSS_CAPABILITY_PRIVACY = 0x1 << 4;
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Short Preamble.
+ */
+ public static final int BSS_CAPABILITY_SHORT_PREAMBLE = 0x1 << 5;
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): PBCC.
+ */
+ public static final int BSS_CAPABILITY_PBCC = 0x1 << 6;
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Channel Agility.
+ */
+ public static final int BSS_CAPABILITY_CHANNEL_AGILITY = 0x1 << 7;
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Spectrum Management.
+ */
+ public static final int BSS_CAPABILITY_SPECTRUM_MANAGEMENT = 0x1 << 8;
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): QoS.
+ */
+ public static final int BSS_CAPABILITY_QOS = 0x1 << 9;
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Short Slot Time.
+ */
+ public static final int BSS_CAPABILITY_SHORT_SLOT_TIME = 0x1 << 10;
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): APSD.
+ */
+ public static final int BSS_CAPABILITY_APSD = 0x1 << 11;
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Radio Management.
+ */
+ public static final int BSS_CAPABILITY_RADIO_MANAGEMENT = 0x1 << 12;
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): DSSS-OFDM.
+ */
+ public static final int BSS_CAPABILITY_DSSS_OFDM = 0x1 << 13;
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Delayed Block Ack.
+ */
+ public static final int BSS_CAPABILITY_DELAYED_BLOCK_ACK = 0x1 << 14;
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Immediate Block Ack.
+ */
+ public static final int BSS_CAPABILITY_IMMEDIATE_BLOCK_ACK = 0x1 << 15;
+
/**
* Returns the capabilities of the AP repseresented by this scan result as advertised in the
* received probe response or beacon.
*
- * This is a bit mask describing the capabilities of a BSS. See IEEE Std 802.11: 9.4.1.4:
- * Bit 0 - ESS
- * Bit 1 - IBSS
- * Bit 2 - CF Pollable
- * Bit 3 - CF-Poll Request
- * Bit 4 - Privacy
- * Bit 5 - Short Preamble
- * Bit 6 - PBCC
- * Bit 7 - Channel Agility
- * Bit 8 - Spectrum Management
- * Bit 9 - QoS
- * Bit 10 - Short Slot Time
- * Bit 11 - APSD
- * Bit 12 - Radio Measurement
- * Bit 13 - DSSS-OFDM
- * Bit 14 - Delayed Block Ack
- * Bit 15 - Immediate Block Ack
+ * This is a bit mask describing the capabilities of a BSS. See IEEE Std 802.11: 9.4.1.4: one
+ * of the {@code BSS_CAPABILITY_*} flags.
*
* @return a bit mask of capabilities.
*/
- @NonNull public int getCapabilities() {
+ @BssCapabilityBits public int getCapabilities() {
return capability;
}
diff --git a/wifi/java/android/net/wifi/wificond/NativeWifiClient.java b/wifi/java/android/net/wifi/wificond/NativeWifiClient.java
index 916c11579075..9ad2a2769add 100644
--- a/wifi/java/android/net/wifi/wificond/NativeWifiClient.java
+++ b/wifi/java/android/net/wifi/wificond/NativeWifiClient.java
@@ -17,11 +17,13 @@
package android.net.wifi.wificond;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.net.MacAddress;
import android.os.Parcel;
import android.os.Parcelable;
-import java.util.Arrays;
+import java.util.Objects;
/**
* Structure providing information about clients (STAs) associated with a SoftAp.
@@ -30,16 +32,21 @@ import java.util.Arrays;
*/
@SystemApi
public final class NativeWifiClient implements Parcelable {
+ private final MacAddress mMacAddress;
+
/**
- * The raw bytes of the MAC address of the client (STA) represented by this object.
+ * The MAC address of the client (STA) represented by this object. The MAC address may be null
+ * in case of an error.
*/
- @NonNull public final byte[] macAddress;
+ @Nullable public MacAddress getMacAddress() {
+ return mMacAddress;
+ }
/**
* Construct a native Wi-Fi client.
*/
- public NativeWifiClient(@NonNull byte[] macAddress) {
- this.macAddress = macAddress;
+ public NativeWifiClient(@Nullable MacAddress macAddress) {
+ this.mMacAddress = macAddress;
}
/** override comparator */
@@ -50,13 +57,13 @@ public final class NativeWifiClient implements Parcelable {
return false;
}
NativeWifiClient other = (NativeWifiClient) rhs;
- return Arrays.equals(macAddress, other.macAddress);
+ return Objects.equals(mMacAddress, other.mMacAddress);
}
/** override hash code */
@Override
public int hashCode() {
- return Arrays.hashCode(macAddress);
+ return mMacAddress.hashCode();
}
/** implement Parcelable interface */
@@ -71,7 +78,7 @@ public final class NativeWifiClient implements Parcelable {
*/
@Override
public void writeToParcel(@NonNull Parcel out, int flags) {
- out.writeByteArray(macAddress);
+ out.writeByteArray(mMacAddress.toByteArray());
}
/** implement Parcelable interface */
@@ -79,9 +86,11 @@ public final class NativeWifiClient implements Parcelable {
new Parcelable.Creator<NativeWifiClient>() {
@Override
public NativeWifiClient createFromParcel(Parcel in) {
- byte[] macAddress = in.createByteArray();
- if (macAddress == null) {
- macAddress = new byte[0];
+ MacAddress macAddress;
+ try {
+ macAddress = MacAddress.fromBytes(in.createByteArray());
+ } catch (IllegalArgumentException e) {
+ macAddress = null;
}
return new NativeWifiClient(macAddress);
}
diff --git a/wifi/java/android/net/wifi/wificond/WifiCondManager.java b/wifi/java/android/net/wifi/wificond/WifiCondManager.java
index 7a31a5afab05..61f18e0b7191 100644
--- a/wifi/java/android/net/wifi/wificond/WifiCondManager.java
+++ b/wifi/java/android/net/wifi/wificond/WifiCondManager.java
@@ -41,19 +41,16 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.stream.Collectors;
/**
- * This class encapsulates the interface the wificond (Wi-Fi Conductor) daemon presents to the
- * Wi-Fi framework. The interface is only for use by the Wi-Fi framework and access is protected
- * by SELinux permissions: only the system server and wpa_supplicant can use WifiCondManager.
+ * This class encapsulates the interface the wificond daemon presents to the Wi-Fi framework. The
+ * interface is only for use by the Wi-Fi framework and access is protected by SELinux permissions.
*
* @hide
*/
@@ -371,7 +368,7 @@ public class WifiCondManager {
public void onConnectedClientsChanged(NativeWifiClient client, boolean isConnected) {
if (mVerboseLoggingEnabled) {
Log.d(TAG, "onConnectedClientsChanged called with "
- + client.macAddress + " isConnected: " + isConnected);
+ + client.getMacAddress() + " isConnected: " + isConnected);
}
Binder.clearCallingIdentity();
@@ -1046,13 +1043,13 @@ public class WifiCondManager {
* WifiScanner.WIFI_BAND_5_GHZ
* WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY
* WifiScanner.WIFI_BAND_6_GHZ
- * @return frequencies List of valid frequencies (MHz), or an empty list for error.
+ * @return frequencies vector of valid frequencies (MHz), or an empty array for error.
* @throws IllegalArgumentException if band is not recognized.
*/
- public @NonNull List<Integer> getChannelsMhzForBand(@WifiAnnotations.WifiBandBasic int band) {
+ public @NonNull int[] getChannelsMhzForBand(@WifiAnnotations.WifiBandBasic int band) {
if (mWificond == null) {
Log.e(TAG, "No valid wificond scanner interface handler");
- return Collections.emptyList();
+ return new int[0];
}
int[] result = null;
try {
@@ -1076,9 +1073,9 @@ public class WifiCondManager {
Log.e(TAG, "Failed to request getChannelsForBand due to remote exception");
}
if (result == null) {
- return Collections.emptyList();
+ result = new int[0];
}
- return Arrays.stream(result).boxed().collect(Collectors.toList());
+ return result;
}
/** Helper function to look up the interface handle using name */
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index 847040ca914a..853212aafcdf 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -2391,4 +2391,14 @@ public class WifiManagerTest {
assertFalse(mWifiManager.isScanThrottleEnabled());
verify(mWifiService).isScanThrottleEnabled();
}
+
+ @Test
+ public void testAutoWakeup() throws Exception {
+ mWifiManager.setAutoWakeupEnabled(true);
+ verify(mWifiService).setAutoWakeupEnabled(true);
+
+ when(mWifiService.isAutoWakeupEnabled()).thenReturn(false);
+ assertFalse(mWifiManager.isAutoWakeupEnabled());
+ verify(mWifiService).isAutoWakeupEnabled();
+ }
}
diff --git a/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java b/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java
index 32105be6ae4c..b745a341b459 100644
--- a/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java
@@ -38,6 +38,7 @@ import static org.mockito.Mockito.when;
import android.app.AlarmManager;
import android.app.test.TestAlarmManager;
import android.content.Context;
+import android.net.MacAddress;
import android.net.wifi.ScanResult;
import android.net.wifi.SoftApInfo;
import android.net.wifi.WifiConfiguration;
@@ -65,8 +66,6 @@ import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -121,7 +120,8 @@ public class WifiCondManagerTest {
private static final String TEST_QUOTED_SSID_2 = "\"testSsid2\"";
private static final int[] TEST_FREQUENCIES_1 = {};
private static final int[] TEST_FREQUENCIES_2 = {2500, 5124};
- private static final byte[] TEST_RAW_MAC_BYTES = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05};
+ private static final MacAddress TEST_RAW_MAC_BYTES = MacAddress.fromBytes(
+ new byte[]{0x00, 0x01, 0x02, 0x03, 0x04, 0x05});
private static final List<byte[]> SCAN_HIDDEN_NETWORK_SSID_LIST =
new ArrayList<byte[]>() {{
@@ -742,43 +742,11 @@ public class WifiCondManagerTest {
verify(deathHandler).run();
// The handles should be cleared after death.
- assertEquals(0, mWificondControl.getChannelsMhzForBand(WifiScanner.WIFI_BAND_5_GHZ).size());
+ assertEquals(0, mWificondControl.getChannelsMhzForBand(WifiScanner.WIFI_BAND_5_GHZ).length);
verify(mWificond, never()).getAvailable5gNonDFSChannels();
}
/**
- * Verify primitive array to list translation of channel API.
- */
- @Test
- public void testGetChannels() throws Exception {
- int[] resultsEmpty = new int[0];
- int[] resultsSingle = new int[]{100};
- int[] resultsMore = new int[]{100, 200};
-
- List<Integer> emptyList = Collections.emptyList();
- List<Integer> singleList = Arrays.asList(100);
- List<Integer> moreList = Arrays.asList(100, 200);
-
- when(mWificond.getAvailable2gChannels()).thenReturn(null);
- assertEquals(mWificondControl.getChannelsMhzForBand(WifiScanner.WIFI_BAND_24_GHZ),
- emptyList);
- assertEquals(mWificondControl.getChannelsMhzForBand(WifiScanner.WIFI_BAND_5_GHZ),
- emptyList);
-
- when(mWificond.getAvailable2gChannels()).thenReturn(resultsEmpty);
- assertEquals(mWificondControl.getChannelsMhzForBand(WifiScanner.WIFI_BAND_24_GHZ),
- emptyList);
-
- when(mWificond.getAvailable2gChannels()).thenReturn(resultsSingle);
- assertEquals(mWificondControl.getChannelsMhzForBand(WifiScanner.WIFI_BAND_24_GHZ),
- singleList);
-
- when(mWificond.getAvailable2gChannels()).thenReturn(resultsMore);
- assertEquals(mWificondControl.getChannelsMhzForBand(WifiScanner.WIFI_BAND_24_GHZ),
- moreList);
- }
-
- /**
* sendMgmtFrame() should fail if a null callback is passed in.
*/
@Test